The Windows SQLite unit job failed because two new copyto tests asserted on
raw POSIX dest strings. On Windows `str(Path("/tmp/research"))` renders with
backslashes, so the copyto dest is `\tmp\research/notes/...` (rclone accepts the
mixed separators — the product is fine). Compare the local dest via `Path(...)`
like the other tests, which normalizes separators cross-platform.
Signed-off-by: phernandez <paul@basicmachines.co>
From the Codex and claude-review automated reviews on PR #917:
- project_diff: fail fast on fatal `rclone check` errors. A non-zero exit with
no combined listing (auth/network/missing-remote) previously produced an empty
plan, so the transfer ran as a no-op and reported success. Now raises
RcloneError with rclone's stderr. (Codex P2 / claude-review #2)
- sync-setup "Next steps": stop pointing every user at the now-Personal-only
`bm cloud sync`; lead with the Team-safe `pull`/`push` and note bisync is
Personal-only. (claude-review #1)
- Cosmetic: capitalize the push/pull abort headlines for consistency; document
the two ConflictStrategy definitions (Typer enum vs engine Literal). (nits)
- Tests: cover keep-local+pull and keep-cloud+push (preserve-destination cases),
project_transfer aborting on a mid-loop conflict-copy failure, and project_diff
raising on a fatal check error vs not raising on a normal differences exit.
Signed-off-by: phernandez <paul@basicmachines.co>
Update docs/cloud-cli.md for the docs site:
- Add a Team Workspaces push/pull section (additive, git-style transfers,
the --on-conflict {fail|keep-local|keep-cloud|keep-both} resolution, and the
no-baseline limitations tracked by #862).
- Mark `sync` and `bisync` as Personal-workspace-only mirrors and point Team
users at push/pull, with a Personal-vs-Team overview table.
- Fix stale command namespace: `bm project sync|bisync|check|bisync-reset` ->
`bm cloud ...` (these live under `bm cloud`, not `bm project`).
- Add push/pull to the command reference and the summary workflows.
Signed-off-by: phernandez <paul@basicmachines.co>
Second code-review finding (PLAUSIBLE): project_diff detects conflicts with
`rclone check` (content/hash), but project_copy used `rclone copy` with its
default size+modtime comparison. On an overwrite strategy (keep-cloud on pull /
keep-local on push), copy could skip a file the diff had flagged as a conflict
when sizes matched and the destination was not older — silently ignoring the
user's explicit choice.
Add `--checksum` to the overwrite copy so the transfer decision uses the same
content basis as detection. New-only mode is unaffected: `--ignore-existing`
skips by existence, so the comparison basis is irrelevant there. project_sync
(the Personal-only mirror) is untouched and keeps its default comparison.
Signed-off-by: phernandez <paul@basicmachines.co>
Code-review finding: project_copy_file built its `rclone copyto` command
inline and omitted `--local-no-preallocate`. On a `pull --on-conflict keep-both`
that copyto writes the conflict copy to the local filesystem, which is exactly
the case the flag guards (NUL byte padding on virtual filesystems such as
Google Drive File Stream). Every other transfer path adds it via
_build_transfer_cmd; bring copyto in line.
Signed-off-by: phernandez <paul@basicmachines.co>
Adds `bm cloud push` and `bm cloud pull` as git-style, fail-safe transfer
primitives that are usable on Team workspaces (issue #858), and restricts the
destructive `bm cloud sync` mirror to Personal workspaces.
Why
- `bm cloud sync` is a destructive local->cloud mirror; on a shared Team bucket
it can delete a teammate's files. The only pull path was two-way `bisync`,
which is already Personal-only (#849).
- Teams need a safe way to fetch teammates' notes and add their own without one
stale local tree becoming authoritative for shared cloud state.
What
- push = `rclone copy` local->cloud, pull = `rclone copy` cloud->local. Both are
additive (never delete on the destination), so neither can damage shared state.
- Conflicts (a file that differs on both sides) abort by default and list the
paths, like git refusing to clobber local changes / rejecting a stale push.
`--on-conflict {fail|keep-local|keep-cloud|keep-both}` lets the user decide;
no vague --force, no silent winner.
- `sync` now requires a Personal workspace; its guard and the bisync guard point
Team users at push/pull.
Limitations (surfaced in --help and command output, tracked by #862):
- No sync baseline yet, so deletions are not propagated and every divergence is
treated as a conflict rather than auto-resolved. Real three-way merge needs
the per-client manifest + Tigris snapshot baseline designed in #862.
Implementation
- rclone_commands.py: shared `_build_transfer_cmd`/`_transfer_endpoints`;
refactor `project_sync` onto them (no behavior change); add `project_diff`
(conflict detection via `rclone check --combined`), `project_copy`,
`project_copy_file`, and `project_transfer` (strategy dispatch).
- project_sync.py: new `push`/`pull` commands (ungated, Team-safe) with
`--on-conflict`/`--dry-run`; gate `sync` to Personal via a per-command guard
message.
- Tests at the rclone-argv and CLI-command levels; existing sync/bisync tests
updated for the new gating and messages.
Signed-off-by: phernandez <paul@basicmachines.co>
L2-normalizes FastEmbed output vectors at the provider boundary so SQLite vector scoring keeps its unit-vector contract for custom FastEmbed models such as multilingual MiniLM variants.
Zero vectors are preserved as-is to avoid division errors, and the provider tests cover both non-unit vectors and zero-vector behavior.
Verification:
- uv run pytest tests/repository/test_fastembed_provider.py -q
- uv run ruff check src/basic_memory/repository/fastembed_provider.py tests/repository/test_fastembed_provider.py
- uv run ruff format --check src/basic_memory/repository/fastembed_provider.py tests/repository/test_fastembed_provider.py
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: tk-pkm111 <133480534+tk-pkm111@users.noreply.github.com>
Signed-off-by: phernandez <paul@basicmachines.co>
Adds LiteLLM as a semantic embedding provider, including provider configuration, vector normalization, live-provider evaluation tooling, and documentation for OpenAI, Cohere, Azure Foundry, Azure OpenAI, and NVIDIA NIM-style cases.
Maintainer follow-up on this PR added provider hardening, asymmetric document/query embedding support, dimension-forwarding controls, SQLite/Postgres vector invalidation coverage, and the repeatable live LiteLLM harness.
Verification:
- Full base-repo Tests workflow passed for 3d4e092ceb: https://github.com/basicmachines-co/basic-memory/actions/runs/27072071785
- Live LiteLLM harness passed locally for OpenAI, Cohere, and Azure Foundry.
Co-authored-by: Aarish Alam <arishalam121@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: RheagalFire <arishalam121@gmail.com>
Signed-off-by: phernandez <paul@basicmachines.co>
Review feedback on #876:
- Codex (P2): the SessionStart "Where to write" brief said all auto-capture goes to
captureFolder, but captureFolder is only the PreCompact checkpoint folder. Narrowed
it — checkpoints go to captureFolder; decisions/tasks/notes follow
placementConventions (or topic folders when none are set), so proactive captures
aren't dumped into sessions/ alongside checkpoints.
- Claude review: replaced the em dash in the skills-source-guard echo string with a
hyphen so the instruction's console output stays ASCII (CLAUDE.md compatibility).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
- Project mapping: document local vs cloud project creation — a cloud-workspace
project needs a cloud-connected MCP server and a workspace selector; a purely
local server fails to mkdir the cloud-style path. Pin cloud primaryProject to the
external_id UUID.
- Install shared skills: guard against clobbering a source checkout — if ./skills is
git-tracked and holds memory-* dirs (the skills' own source repo), skip the npx
install instead of overwriting the working copy with published versions.
- Cloud/teams: warn that the SessionStart brief reads only the first 6 shared
projects per session, so order the most relevant first when more are configured.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
- Focus question is now load-bearing: it drives an adaptive light folder-structure
suggestion and is stored. The placement step branches learn (existing project
with notes) vs suggest (new/empty project) instead of only learning.
- Seed schema notes via the write_note `metadata` param instead of content
frontmatter. The cloud write path silently coerces nested YAML to the string
'[object Object]', corrupting schema/settings (basic-memory-cloud#1000); the
metadata param round-trips correctly on both local and cloud.
- Surface placementConventions + captureFolder in the SessionStart brief so the
output style's "follow stored placement conventions" reflex has something to
follow — previously written by setup but never shown to Claude (dead config).
- Add a post-settings smoke test (run the hook's recall query, confirm routing)
and a restart prompt gated on outputStyle in the close.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
Codex flagged (review 4397232451) that the hooks locate the CLI via
`command -v basic-memory || command -v bm`, but our own README recommends
connecting the MCP server as `claude mcp add basic-memory -- uvx basic-memory mcp`
— an ephemeral uv run that leaves NO binary on PATH. In that (recommended) setup
both hooks exited before doing anything, so the bridge silently did nothing even
though the MCP server worked.
Both hooks now resolve the CLI invocation as: prefer a `basic-memory`/`bm` binary
(fast), else fall back to `uvx basic-memory`, else `uv tool run basic-memory`, else
silent no-op. The launcher may be multi-token, so the embedded Python splits it with
shlex and prepends it to each command list. The uv cache is already warm from
running the MCP server, so the fallback is cheap.
Verified: with no binary on PATH but uvx present, SessionStart now produces a brief
(previously a silent no-op). Updated the README requirement to note uvx-only works.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
Codex flagged /basic-memory:remember passing a UUID primaryProject as `project`
(review 4397206729) — the same routing bug already fixed in the hooks and the
share skill. Rather than fix only the flagged spot, audited every project-ref
site and made them all consistent:
- remember: route primaryProject as project_id when it's a UUID (was the flag)
- status: same for the primaryProject-scoped search_notes queries
- setup: same for the schema-seed write to primaryProject
share/SKILL.md and both hooks (session-start, pre-compact) already handle it.
That's now every place the plugin routes to a project ref — a project ref may be
a workspace-qualified name (-> project) or an external_id UUID (-> project_id),
and all six sites detect and route accordingly. Plugin validates.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>