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
13 changes: 10 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,23 @@
- **#997**: Added the `bm hook` harness front door (SPEC-55). Lifecycle verbs
(`session-start`, `pre-compact`) move the plugin hook logic into the package
behind per-harness stdin adapters, with the session brief fenced as reference
data. Opt-in envelope capture (`captureEvents: true`, fail-closed) records
redacted lifecycle events into a local inbox WAL, projected deterministically
by `bm hook flush`; `bm hook status` shows the surface. `bm hook install` /
data. Default-on envelope capture records bounded lifecycle metadata into a
local inbox WAL, and `bm hook flush` archives it locally; `bm hook status`
shows the surface. `captureEvents: false` disables capture. `bm hook install` /
`bm hook remove` wire the hooks into user-level harness config for standalone
users with ownership-tagged, surgical merging. The Claude Code and Codex
plugin hooks are now zero-logic PEP 723 uv scripts (`uv run --script`) that
invoke `basic-memory hook` in-process; their dependency floor is bumped by
release tooling, and `BM_BIN` overrides the uv-managed environment for
development.

### Maintenance

- Removed the hook-specific redaction subsystem, its `detect-secrets`
dependency, and the retired lifecycle projector compatibility module.
- Kept Milvus as a first-party optional vector backend while removing the
unused Python entry-point registry for separately packaged vector adapters.

## v0.22.1 (2026-06-12)

Follow-up patch to v0.22.0. Fixes project and default-project resolution on
Expand Down
88 changes: 12 additions & 76 deletions docs/semantic-search.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ All settings are fields on `BasicMemoryConfig` and can be set via environment va
| Config Field | Env Var | Default | Description |
|---|---|---|---|
| `semantic_search_enabled` | `BASIC_MEMORY_SEMANTIC_SEARCH_ENABLED` | Auto (`true` when semantic deps are available) | Enable semantic search. Required before vector/hybrid modes work. |
| `semantic_vector_index` | `BASIC_MEMORY_SEMANTIC_VECTOR_INDEX` | `"pgvector"` | Postgres vector storage adapter. `"pgvector"` is built in, `"milvus"` is available through the `basic-memory[milvus]` extra, and other names resolve through the `basic_memory.semantic_vector_indexes` Python entry-point group. SQLite always uses its built-in `sqlite-vec` adapter. |
| `semantic_vector_index` | `BASIC_MEMORY_SEMANTIC_VECTOR_INDEX` | `"pgvector"` | Postgres vector storage adapter: `"pgvector"` or first-party `"milvus"` through the `basic-memory[milvus]` extra. SQLite always uses its built-in `sqlite-vec` adapter. |
| `milvus_uri` | `BASIC_MEMORY_MILVUS_URI` | Unset | Milvus, Milvus Lite, or Zilliz Cloud connection URI. Required when `semantic_vector_index="milvus"`. |
| `milvus_token` | `BASIC_MEMORY_MILVUS_TOKEN` | Unset | Optional Milvus or Zilliz Cloud authentication token. |
| `milvus_timeout_seconds` | `BASIC_MEMORY_MILVUS_TIMEOUT_SECONDS` | `30.0` | Finite per-operation timeout for Milvus and Zilliz client calls. Increase it for unusually slow deployments. |
Expand Down Expand Up @@ -473,7 +473,7 @@ bm reindex -p my-project
- **Dimension change**: After changing `semantic_embedding_dimensions`
- **LiteLLM role change**: After changing `semantic_embedding_document_input_type` or `semantic_embedding_query_input_type`
- **Literal prefix change**: After changing `semantic_embedding_document_prefix` or `semantic_embedding_query_prefix`
- **Vector index change**: After completing the external-adapter cleanup procedure below and changing `semantic_vector_index`
- **Vector index change**: After completing the vector-store cleanup procedure below and changing `semantic_vector_index`

The reindex command shows progress with embedded/skipped/error counts:

Expand Down Expand Up @@ -538,7 +538,7 @@ The sqlite-vec extension is loaded per-connection. Vector tables are created laz

The Alembic migration creates the dimension-independent chunks table. The embeddings table and HNSW index are deferred to runtime because they depend on the configured vector dimensions.

## Milvus and Pluggable Vector Indexes
## Milvus Vector Index

Postgres deployments can replace pgvector storage and nearest-neighbour lookup without
replacing Basic Memory's SQL repositories or embedding providers. Milvus is the first-party
Expand Down Expand Up @@ -572,94 +572,30 @@ an existing collection uses another dimension or an incompatible schema, Basic M
it and fails initialization instead of deleting shared vectors during a rolling deployment.
Coordinate the exact collection migration, then run `bm reindex --embeddings`.

Other provider names still resolve through the extension entry-point contract. A configured
extension that is missing, duplicated, invalid, or returns an incompatible adapter fails
explicitly at startup. Basic Memory does not silently fall back to pgvector, because doing so
would split vectors across stores while appearing healthy.

SQLite remains automatic in this version: local SQLite databases always select `sqlite-vec`, even
if `semantic_vector_index` is set. The selector controls Postgres-backed runtimes only.

### Extension Package Contract

A separately distributed package registers one factory under the
`basic_memory.semantic_vector_indexes` entry-point group:

```toml
[project.entry-points."basic_memory.semantic_vector_indexes"]
qdrant = "acme_basic_memory_qdrant:create_index"
```

The factory receives an explicit scope and the validated Basic Memory configuration:

```python
from basic_memory.config import BasicMemoryConfig
from basic_memory.repository.semantic_vector_index import (
SemanticVectorIndex,
VectorIndexScope,
)


def create_index(
*,
scope: VectorIndexScope,
app_config: BasicMemoryConfig,
) -> SemanticVectorIndex:
...
```

`VectorIndexScope` contains a stable, credential-free database namespace, project ID, embedding
identity, and vector dimensions. Extensions must isolate physical storage by
`scope.storage_key`, which contains only the stable database namespace and project ID.
`embedding_identity` and `dimensions` describe the current vector schema for validation and
initialization; adapters must not use those mutable fields to create a second unreachable
project collection when the embedding configuration changes. Extensions own their client
lifecycle, credentials, collection/index creation, vector persistence, and nearest-neighbour
implementation.

The returned `SemanticVectorIndex` has five asynchronous operations:

- `initialize()` validates or creates backend storage.
- `upsert(records)` idempotently writes vectors by `(entity_id, chunk_key)` for each record's
`source_hash` generation.
- `delete(records)` removes stable keys only for each record's `source_hash` generation; stale or
missing records are successful no-ops.
- `delete_entity(entity_id)` removes all vectors for one entity in the scope.
- `search(query, limit)` returns stable keys with normalized cosine similarity in `[0, 1]`.

The adapter never receives a SQLAlchemy session and never calls the embedding provider. Basic
Memory owns chunking and embedding, while the extension owns vector storage and lookup.
The built-in pgvector and sqlite-vec adapters additionally remove each pending SQL manifest row in
the same database transaction as its vector so no newer generation can enter between those steps.
Each `VectorRecord` and `VectorDeletion` carries the SHA-256 source generation that produced its
value. Basic Memory holds the matching SQL manifest locked across extension adapter I/O and the
ready-state transition or deletion (`FOR UPDATE` on Postgres and a conditional write lock on
SQLite), so an older overlapping sync cannot overwrite or remove a newer generation under the same
stable adapter key.

Adapters may also implement the separate `SemanticVectorIndexReconciler` capability. After a
vector reindex, Basic Memory passes it the complete set of current ready keys so the adapter can
delete scoped external orphans. Keeping reconciliation separate preserves the narrow required
storage protocol while allowing external stores to reclaim records left by interrupted deletes or
ready-state commits.
Milvus implements Basic Memory's internal `SemanticVectorIndex` storage boundary. Basic Memory
continues to own chunking, embeddings, and the SQL manifest; Milvus owns only vector persistence,
nearest-neighbour lookup, and scoped orphan cleanup.

### SQL Manifest and Failure Recovery

`search_vector_chunks` remains the authoritative manifest even when vectors live in an external
store. Each row records the selected `vector_index`, embedding identity, stable chunk key, and an
`embedding_status` of `pending` or `ready`.

Writes and deletes commit `pending` before calling the adapter. A successful adapter operation then
Writes and deletes commit `pending` before calling the vector index. A successful operation then
makes the manifest row ready or removes it. Vector writes are generation checked inside built-in
adapter transactions; extension writes retain the manifest lock across adapter I/O. If the external
SQL adapter transactions; Milvus writes retain the manifest lock across client I/O. If the external
operation fails, the pending row is not searchable and the next sync safely retries the idempotent
operation. Adapter search results are hydrated only through current, ready manifest rows, so stale
or orphaned external matches fail closed.

Basic Memory deliberately refuses to mutate manifest rows owned by a different external adapter.
Before switching `semantic_vector_index`, keep the old adapter configured and use that extension's
project-scope administrative cleanup to remove the old vectors. Remove the corresponding
Basic Memory deliberately refuses to mutate manifest rows owned by a different vector index.
Before switching `semantic_vector_index`, keep the old index configured and remove its
project-scoped vectors. Remove the corresponding
`search_vector_chunks` manifest rows only after the external cleanup succeeds. Then switch the
configured adapter and run `bm reindex --embeddings` to populate the new store. If configuration
was switched too early, restore the old adapter first; the ownership check will continue to fail
was switched too early, restore the old index first; the ownership check will continue to fail
closed until cleanup is completed.
11 changes: 7 additions & 4 deletions plugins/claude-code/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,17 +98,20 @@ Memory's durable graph**, rather than a memory layer of its own. See
by release tooling) and the script invokes
`basic-memory hook <event> --harness claude` in-process with the hook JSON
on stdin. `BM_BIN` overrides the uv-managed environment for development.
The brief/checkpoint logic lives in the released package; opt-in
`captureEvents: true` additionally records redacted event envelopes to a
local inbox. uv is the required prerequisite; the first run fetches from
PyPI, later runs use uv's cache.
The brief/checkpoint logic lives in the released package; lifecycle envelopes
containing bounded metadata are captured by default and can be disabled with
`captureEvents: false`. uv is the required prerequisite; the first run fetches
from PyPI, later runs use uv's cache.
- **SessionStart hook now nudges toward `/basic-memory:bm-setup` on first run** — when
no `basicMemory` config block is present in either settings file. The nudge
survives a failed/empty task query (so a brand-new user with no project yet still
sees it), and stops once setup writes the config. (Phase 3)

### Removed (clean break)

- Hook-specific secret scanning and the `redactKeys` / `redactPaths` settings.
Lifecycle envelopes now stay small by construction instead of carrying a
general-purpose redaction subsystem.
- The six bundled skills (`placement`, `knowledge-capture`, `knowledge-organize`,
`continue-conversation`, `research`, `edit-note`). Equivalent, framework-agnostic
workflows live in the top-level [`skills/`](../../skills) package
Expand Down
14 changes: 6 additions & 8 deletions plugins/claude-code/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,11 @@ local checkout. Two disclosures:
- **Network fetch on first run.** uv downloads `basic-memory` from PyPI at a
pinned minimum version (bumped by release tooling); later runs use uv's
cache.
- **Event capture is opt-in and off by default.** Setting `captureEvents: true`
(the JSON boolean — strings never enable it) records redacted lifecycle-event
envelopes to a local inbox under your Basic Memory home. Inspect with
`basic-memory hook status`, archive locally with `basic-memory hook flush`.
The lifecycle trace never becomes a graph note.
- **Event capture is on by default.** Setting `captureEvents: false` disables
lifecycle-event envelopes. Other values fail closed. Envelopes contain bounded
lifecycle metadata and live in a local inbox under your Basic Memory home.
Inspect with `basic-memory hook status`, archive locally with
`basic-memory hook flush`. The lifecycle trace never becomes a graph note.

Every failure path exits 0 — the hooks stay invisible rather than disrupt a
session.
Expand Down Expand Up @@ -151,9 +151,7 @@ settings (or select it via `/config`).
| `preCompactCapture` | `extractive` | How checkpoints are produced |
| `sessionProfile` | `general` | `coding` makes checkpoints schema-backed `coding_session` notes with required Git identity |
| `repository` | _(none)_ | User-confirmed stable repository identifier (`owner/name`); required for the `coding` profile |
| `captureEvents` | `false` | Opt-in: record redacted lifecycle-event envelopes to the local inbox (see `basic-memory hook status` / `flush`). Only the JSON boolean `true` enables it. |
| `redactKeys` | `[]` | Additional payload keys to redact before an event enters the local inbox |
| `redactPaths` | `[]` | Additional paths to redact from working-directory and path-bearing capture content |
| `captureEvents` | `true` | Record bounded lifecycle-event envelopes to the local inbox (see `basic-memory hook status` / `flush`). Set the JSON boolean `false` to disable it; other values fail closed. |

The plugin seeds schemas for notes the Claude integration writes directly:
`decision`, `task`, and the session type relevant to the selected profile. A
Expand Down
2 changes: 1 addition & 1 deletion plugins/claude-code/hooks/pre_compact.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# dependencies = ["basic-memory>=0.22.1"]
# ///
"""PreCompact hook — the entire hook. All logic (settings resolution, the
extractive checkpoint note, opt-in envelope capture) lives in the released
extractive checkpoint note, lifecycle-envelope capture) lives in the released
basic-memory package behind `basic-memory hook pre-compact`; this script is
only the launcher, and uv resolves the dependency floor declared above.
scripts/update_versions.py bumps that floor at release time.
Expand Down
2 changes: 1 addition & 1 deletion plugins/claude-code/hooks/session_start.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# dependencies = ["basic-memory>=0.22.1"]
# ///
"""SessionStart hook — the entire hook. All logic (settings resolution, the
context brief, opt-in envelope capture) lives in the released basic-memory
context brief, lifecycle-envelope capture) lives in the released basic-memory
package behind `basic-memory hook session-start`; this script is only the
launcher, and uv resolves the dependency floor declared above.
scripts/update_versions.py bumps that floor at release time.
Expand Down
4 changes: 1 addition & 3 deletions plugins/claude-code/settings.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
"recallPrompt": "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 user makes a material decision, capture it as a note with type: decision. Cite permalinks when referencing prior work.",
"preCompactCapture": "extractive",
"sessionProfile": "general",
"captureEvents": false,
"redactKeys": [],
"redactPaths": [],
"captureEvents": true,
"placementConventions": null,
"teamProjects": {
"my-team/notes": { "promoteFolder": "shared" }
Expand Down
27 changes: 10 additions & 17 deletions plugins/claude-code/skills/bm-setup/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,12 @@ Ask only what you can't infer. Cover:
task schemas, so I can find them precisely later — okay?" (See "Seed the
schemas" below.)

6. **Lifecycle-event capture.** "Should I also keep a local, redacted trail of
SessionStart and PreCompact events for diagnostics?" Default to **off**.
Explain that the normal session brief and PreCompact checkpoint work either
way; enabling this adds envelopes to a local inbox until `bm hook flush`
archives them locally. It never creates knowledge-graph notes. Only the JSON
boolean `true` enables capture.
- If enabled, optionally ask for repo-specific `redactKeys` (additional payload
keys) and `redactPaths` (working directories or path-bearing content). The
built-in redaction floor still applies when these lists are empty.
6. **Lifecycle-event capture.** "The plugin keeps a local trail of SessionStart
and PreCompact events for diagnostics by default. Keep that on?" Default to
**on**. Explain that the normal session brief and PreCompact checkpoint work
either way; capture adds bounded lifecycle metadata to a local inbox until
`bm hook flush` archives it locally. It never creates knowledge-graph notes.
Persist the JSON boolean `false` only when the user opts out.
- Capture stays local and personal. It never writes directly to team projects.

7. **How active should I be? (output style)** "Want me to proactively capture —
Expand Down Expand Up @@ -210,9 +207,7 @@ Build the `basicMemory` block from the interview:
"preCompactCapture": "extractive",
"sessionProfile": "coding",
"repository": "owner/name",
"captureEvents": false,
"redactKeys": [],
"redactPaths": [],
"captureEvents": true,
"placementConventions": "<learned or suggested summary, or null>",
"teamProjects": {}
},
Expand All @@ -229,9 +224,7 @@ Only include `outputStyle` if the user opted in. Ask whether this is a **team
default** (write/merge into `.claude/settings.json`, suggest committing it) or
**personal** (`.claude/settings.local.json`). **Merge** into any existing file —
read it, add/replace only the keys above, preserve everything else. Use compact,
valid JSON. Always persist `captureEvents` as a JSON boolean. Empty `redactKeys`
and `redactPaths` lists may be omitted; when present, they must be JSON arrays of
strings.
valid JSON. Always persist `captureEvents` as a JSON boolean.

Writing the `basicMemory` block is also what stops the SessionStart hook's first-run
nudge — the config's presence is the signal that setup has run.
Expand Down Expand Up @@ -264,8 +257,8 @@ ref before closing — don't let the next session's brief come up empty.
Confirm what you did in a few lines: the project mapping, the session profile
(and confirmed repository for a coding setup), which schemas were seeded vs.
already present, whether placement was learned or suggested, the smoke-test
result, whether lifecycle-event capture is enabled, any extra redaction controls,
the shared hook inbox/flush state, and whether the output style is on.
result, whether lifecycle-event capture is enabled, the shared hook inbox/flush
state, and whether the output style is on.

Then handle activation based on the output style:
- **Output style enabled** → it's fixed at session start, so the full capture
Expand Down
4 changes: 1 addition & 3 deletions plugins/claude-code/skills/bm-status/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ This is a quick diagnostic — gather the facts and lay them out; don't over-inv
(default `sessions`), `rememberFolder` (default `bm-remember`),
`preCompactCapture` mode (default `extractive`), `sessionProfile` (default
`general`), `repository` (coding profile only), `captureEvents` (default
`false`), `redactKeys`, and `redactPaths`.
`true`).
- From the **root** settings object (not `basicMemory`): whether `outputStyle` is
`basic-memory` — i.e. whether the capture reflexes are on.

Expand Down Expand Up @@ -73,8 +73,6 @@ you couldn't determine, rather than failing the whole report):
- Session profile: <general | coding>
- Repository: <owner/name or none>
- Event capture: <enabled | disabled>
- Redact keys: <configured count or none>
- Redact paths: <configured count or none>
- Shared hook inbox: <path or unavailable>
- Shared pending envelopes: <count or unavailable>
- Shared archived envelopes: <count or unavailable>
Expand Down
Loading
Loading