Changelog¶
All notable changes to this project are documented in the canonical CHANGELOG.md in the repository. This page reproduces the v1.x and v0.8.x entries for quick reference; older releases (v0.7.x and earlier) are available on GitHub.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[1.2.2] - 2026-07-18¶
Fixed¶
- opencode MCP launch: command path must be absolute (the real reason v1.2.1 didn't work) — Session 53's v1.2.1 release fixed the pgembed bootstrap but missed the actual symptom the user hit. v1.2.1's
initwrotecommand: ["uvx", "--from", "memini-ai-dev", "memini-ai"]into~/.config/opencode/opencode.json. That command depends onuvxbeing in$PATHat MCP-spawn time. But opencode spawns MCP servers with a clean non-interactive environment, which on most distros (Ubuntu/Debian/Fedora) does NOT include/home/<user>/.local/binin$PATH(only login shells do). The MCP server therefore fails instantly with "uvx: command not found" and is never started. Every subsequent call to a memini-ai tool returns "tool not available" — the symptom looks like "embedded server doesn't come up or connect", because there is no server. New helper_resolve_memini_command()ininstaller.pyresolves the launch command at install time usingshutil.which("uvx")and falls back to<home>/.local/bin/memini-ai, writing ABSOLUTE paths into the opencode.json command array. Bothinitandupdateuse the resolver. The MCP server now starts regardless of the user's shell PATH.
[1.2.1] - 2026-07-18¶
Fixed¶
- Re-released as v1.2.1 because the v1.2.0 tag (Session 52, RBAC + SSL + container detection, commit
0985dc1) was already taken. Per AGENTS.md 'Never Retag a Public Release' rule, this is a patch release on top of v1.2.0 with the same fixes below. - Fresh-VM pgembed bootstrap (opencode 30s timeout) —
EmbeddedPGDriver.__init__never created the data dir parent, so the firstpgembed.get_server()call on a fresh machine failed with "Parent directory of pgdata does not exist". ThePostgresDatabase.initialize()3-retry loop (1+2+4s backoff) + the HF model download (~30s) combined to push past opencode's 30s MCP startup timeout. The first ever tool call (query_memoriesetc.) would time out and opencode markedmemini-ai-devas "server unavailable". 3 cascading bugs fixed in_start_new_server: self._data_dir.mkdir(parents=True, exist_ok=True)— auto-create the data dir parent before callingpgembed.get_server()._write_postgres_config()appendsdynamic_library_path = '$libdir:<pgembed_ext_lib_path>'topostgresql.conf(NOTpostgresql.auto.conf— that's only read on ALTER SYSTEM/SIGHUP, not on a fresh server start). Idempotent: drops any prior line we wrote before appending. Skips on the very first init (postgresql.conf doesn't exist yet) — the NEXT call (after initdb) writes it.restart_server_for_new_config()async method: forces the postmaster to stop + reattach so the newdynamic_library_pathtakes effect.pgembed.get_server(cleanup_mode='stop')+ immediate cleanup, then a freshget_server(cleanup_mode=None)to spin up a new postmaster that re-reads postgresql.conf.memini-ai initCLI now callsdriver.restart_server_for_new_config()afterget_uri()so the postmaster picks up the new config before the user runs opencode. Wrapped in try/except so a restart failure doesn't break init.
Why this works¶
- pgembed 0.2.0 ships
vector.soandvectorscale-0.9.0.soat<site-packages>/pgembed/pginstall/lib/postgresql/, but the stock postgresdynamic_library_pathis just'$libdir'(the install's own lib dir), soCREATE EXTENSION vectorcouldn't find the .so file. The pgembedserver.create_extension()API uses psql internally which segfaults on Python 3.13/3.14 (exit 139). Writing topostgresql.auto.conflooked promising but is only read on ALTER SYSTEM/SIGHUP, not on a fresh server start. The only safe path is editingpostgresql.conf(which pgembed never touches after initdb) + restarting the postmaster.
Added¶
- 4 regression tests in
tests/test_pgembed_driver.py::TestEdgeCases: test_start_new_server_creates_parent_data_dir— regression for the "Parent directory of pgdata does not exist" fixtest_start_new_server_writes_postgres_config— verifies the dynamic_library_path line is in postgresql.conf after_start_new_serverrunstest_start_new_server_continues_if_postgres_config_write_fails— OSError on config write must NOT crash server starttest_postgres_config_write_is_idempotent— 3 calls produce 1 linetest_postgres_config_write_skips_first_init— no PG_VERSION = no config write (initdb needs empty dir)- Module-level
EXTENSION_POSTGRES_LIB_PATHimport inpostgres/driver.pyso tests can mock it without needing pgembed installed locally.
Changed¶
pyproject.toml: removed emptyteam = []optional-dependency entry (the team server selection lives in the install script, not as a separate optional dep — keeps the install path unified per user direction in this session).
Quality Gates¶
ruff check src/→ 0 errorspytest tests/→ 942/944 pass (2 pre-existing pgembed-env failures unrelated to these changes)- Verified end-to-end on test-bunty VM (jcharles@192.168.1.86, fresh Ubuntu 24.04, no prior memini-ai data):
memini-ai initfrom cold: 1.5s, postgresql.conf gets the dynamic_library_path linememini-ai --stdio+query_memoriesreturns 0 results in <1s after model loadsSHOW dynamic_library_pathagainst the running postmaster returns the correct bundled pathCREATE EXTENSION vector;+CREATE EXTENSION vectorscale;both succeed against the running postmaster- opencode launch: "init count=8", memini-ai-dev still shows a brief "server unavailable" warning at T+1.4s (the MCP probe timeout) but recovers — was T+33s hard fail before this fix
Process lessons¶
- When MCP startup is the symptom, dig past the startup — the actual failure is usually on the first tool call, not at process spawn.
server.create_extension()looks like the right API for installing pgvector in pgembed, but psql segfaults on 3.13/3.14. Workaround: editpostgresql.conf+ restart.- For tests, mock imports at module level (not inside the function body) so
patch.object(module, "CONSTANT", value)works on CI without the actual package installed.
[1.0.3] - 2026-07-16## [1.0.3] - 2026-07-16¶
Fixed¶
- Docs catch-up —
HANDOFF.md,AGENTS.md,TASKS.md, andCONTEXT.mdwere 9 versions stale (stopped at v0.7.6 / Session 40, 2026-07-10). All 4 docs now reflect v1.0.2 reality: 9 new session entries (Sessions 41-52) in HANDOFF, per-release sections in TASKS, Version History table updated in CONTEXT, and a new CRITICAL section in AGENTS.md explaining the v1.0.0MEMINI_VECTOR_BACKENDrequirement. Backwards-compatible (no code changes). uv.lockdrift from v1.0.2 release — the v1.0.2 commit (ad30e2c) bumpedpyproject.tomlto 1.0.2 but did not regenerateuv.lock, so the lockfile was still pinned atversion = "1.0.0".uv lockregenerated the single-line version stamp; no actual dependency changes.
Added¶
- New CRITICAL section in
AGENTS.mddocumenting the v1.0.0 breaking change:MEMINI_VECTOR_BACKENDis required whenMEMINI_DB_URLis set. Symptom:RuntimeError: memini-ai v1.0.0: MEMINI_DB_URL is set but MEMINI_VECTOR_BACKEND is not.on MCP server start. One-line fix:export MEMINI_VECTOR_BACKEND=postgres-external(preserves v0.8.x behavior; no data migration). 3 config locations to check listed in the CRITICAL block. - DB server healthcheck — in-process
MCPServer.healthcheck()returnsstatus=pass, readbackMatch=True, writeLatencyMs=2.9s, readLatencyMs=0.45ms.get_status:memoryCount=982, thoughtsCount=519, queryLatencyMs=0.67. Livememini-postgreson port 5434 verified 2026-07-16: 986 memories + 519 thoughts, all 13 tables present, 100% healthy.
Quality Gates¶
ruff check src/ tests/→ 0 errorsmypy src/→ 0 errors (53 source files)pytest tests/→ 809 passing (v0.7.8 baseline)- In-process MCP E2E green (healthcheck + get_status + query_memories)
- Live DB: 986 memories + 519 thoughts preserved, zero data loss
- No code changes, no new env vars, no new dependencies
- Commits:
b88dd47(docs),1c7d8ba(uv.lock v1.0.2 sync),ff90815(v1.0.3 bump)
[1.0.2] - 2026-07-16¶
Fixed¶
memini-ai migrateCLI command — 6 bugs fixed insrc/memini_ai/cli.py::_migrate(). v1.0.1 shipped the fixes in the standalone script (scripts/migrate_external_to_embedded.py) but the CLI command (which is whatmemini-ai migrateactually invokes) was still broken. This release brings the CLI to parity with the now-working standalone script:pg_restoreresolved from the system PATH (pg18) instead of pgembed's bundled pg17 binary. For a pg17 target (pgembed), using the system pg18pg_restorecan cause version-compatibility issues. The CLI now resolvespg_restorefrompgembed/pginstall/bin/pg_restore(PostgreSQL 17), falling back to PATH only if pgembed is not importable.pg_dumpcontinues to use the system binary (it must be >= the source server version; pgembed's pg17pg_dumpaborts with "server version mismatch" against a pg18 source).- Did not pre-install
vector+vectorscaleextensions on the target before restore. The dump containsCREATE EXTENSION vector/CREATE EXTENSION vectorscale; pgembed ships them but they must beCREATE EXTENSION'd in the target DB beforepg_restoreruns. The CLI now connects viaasyncpgand runsCREATE EXTENSION IF NOT EXISTSfor both after starting the embedded server. - Did not exclude
timescaledb+timescaledb_toolkitfrom the dump. The source image (timescaledb-ha:pg18) has these installed; pgembed does not, sopg_restorefailed with "extension timescaledb is not available". The CLI now adds--exclude-extension=timescaledb --exclude-extension=timescaledb_toolkitto thepg_dumpcommand. - No post-restore verification. After restore the user had no way to know whether it actually worked. The CLI now runs a verification step that compares per-table row counts between source and target, pulls a random memory and verifies
text+embeddingmatch exactly (using the correct column nametext, notcontent), checks that diskann indexes exist on the target, prints a clear PASS/FAIL summary, and exits 1 on mismatch. The table list is read from the live source schema (not hardcoded). pg_restoreerror handling treated harmless errors as fatal. The shipped code usedcheck=True, which fails on any non-zero exit.pg_restorereturns nonzero for harmless errors (role mismatches, missing extensions, etc). The CLI now usescheck=False, then inspects stderr for realERROR:lines and only fails on those — filtering outtimescaledb-related errors and role/ownership errors (role "..." does not exist,role "..." already exists).- No
--dry-runflag. Useful for "is this going to work?" pre-flight checks.memini-ai migrate --dry-runnow runs the dump, counts source rows, starts the embedded server, pre-installs extensions, counts target rows, and exits 0 without restoring.
Notes for users who tried the v1.0.1 standalone script¶
- If you ran
scripts/migrate_external_to_embedded.pydirectly (rather thanmemini-ai migrate), your migration worked correctly — v1.0.1 fixed the standalone script. This release fixes the CLI command so both paths now behave identically. - No schema changes, no new dependencies.
asyncpgwas already a dependency. - No version bump in this commit — the orchestrator runs
bumpversion --patch --applyas a separate step.
[1.0.1] - 2026-07-16¶
Fixed¶
memini-ai migratescript — 6 bugs fixed inscripts/migrate_external_to_embedded.pythat prevented the v1.0.0 migration command from working end-to-end:- Used system pg_dump/pg_restore (pg18) instead of pgembed's bundled pg17 binaries. The script now resolves
pg_dumpfrom the system PATH (it must be >= the source server version — pgembed's pg17pg_dumpaborts with "server version mismatch" against a pg18 source) andpg_restorefrom the pgembed install (pgembed/pginstall/bin/, PostgreSQL 17) so the restore matches the pg17 embedded target. Theprefer="system"/prefer="pgembed"resolution is explicit per binary. parse_db_urldid not extract?host=query param for Unix socket URIs. The embedded server URI ispostgresql://postgres:@/postgres?host=/path/to/data;urlparse()putshost=in.query, not.hostname, sopg_restore -h localhostfailed with "Connection refused". The parser now regex-extracts?host=and passes it as-htopg_restore.- Did not pre-install extensions on target before restore. The dump contains
CREATE EXTENSION vector/CREATE EXTENSION vectorscale; pgembed ships them but they must beCREATE EXTENSION'd in the target DB beforepg_restoreruns. The script now connects viaasyncpgand runsCREATE EXTENSION IF NOT EXISTSforvectorandvectorscaleafter starting the embedded server. - Did not exclude timescaledb extensions from the dump. The source image (
timescaledb-ha:pg18) hastimescaledb+timescaledb_toolkitinstalled; pgembed does not.pg_restorefailed with "extension timescaledb is not available". The script now adds--exclude-extension=timescaledb --exclude-extension=timescaledb_toolkitto thepg_dumpcommand. request_explicit_shutdown()is sync, not async.EmbeddedPGDriver.request_explicit_shutdown()is a plaindef, soawait driver.request_explicit_shutdown()would crash withTypeError: object NoneType can't be used in 'await' expression. The script now calls it WITHOUTawait, beforeawait driver.shutdown(), to ensure the embedded server actually stops at the end of the migration.- Spot-check column was
textnotcontent. Thememoriestable column istext; any verification query usingcontentfailed withUndefinedColumnError. The new verification step counts rows per table, pulls a random memory and comparestext+embeddingexactly between source and target, and confirms the diskann indexes exist on the target.
Added¶
--dry-runflag formemini-ai migrate: runs the dump, counts source rows, starts the embedded server, pre-installs extensions, counts target rows, then exits WITHOUT restoring. Useful for "is this going to work?" pre-flight checks. The dump file is left on disk for inspection.- Post-restore verification step: per-table row-count comparison (source vs target), random memory spot-check (
text+embeddingexact match), and diskann index existence check on the target. Prints a clear PASS/FAIL summary and exits with code 2 if verification fails (0 on success). - Better error messages: embedded server start failure prints the actual exception;
pg_restorestderr is filtered for realERROR:lines (timescaledb/extension warnings are ignored) before deciding to fail; dump file size and restore duration are printed. - PGPASSWORD via subprocess env instead of relying on
~/.pgpass; cleaner output with KB + seconds metrics.
Notes¶
- No code changes outside
scripts/migrate_external_to_embedded.pyandCHANGELOG.md. No new dependencies (stdlib +asyncpgwhich was already a dependency).ruff checkandast.parseclean. - No version bump in this commit — the orchestrator will run
bumpversion --patch --applyas a separate step per the release discipline inAGENTS.md.
[1.0.0] - 2026-07-16¶
Breaking changes¶
- Embedded PostgreSQL is now the default backend (v0.8.2 used external Postgres). The new
pgembeddriver starts an in-process Postgres 17 server on first query. MEMINI_VECTOR_BACKENDmust be set explicitly if you haveMEMINI_DB_URLconfigured. v0.8.2 users who setMEMINI_DB_URLto an external Postgres will get aRuntimeErroron startup with clear remediation. See "Migrating from v0.8.2" below.- Python 3.12+ required (was 3.11+). pgembed 0.2.0 requires Python 3.12+.
PostgresDatabase.__init__now takes adriverparameter instead ofdb_url. This is an internal change — most users go throughcreate_database()which is unchanged.- Data directory location changed from
~/.memini-ai/pgembed/to~/.local/share/memini-ai/pgembed/data(XDG Base Directory spec compliant). Theserver.jsonstate file stays in~/.memini-ai/pgembed/.
Added¶
pgembedbackend (default): in-process PostgreSQL 17 with pgvector + vectorscale + pg_textsearch. No Docker required.postgres-externalbackend: existing Docker/team server behavior, preserved.- Driver pattern:
DatabaseDriverProtocol withEmbeddedPGDriverandExternalPGDriverimplementations. - Multi-process server sharing: one embedded Postgres shared by all memini-ai processes on the same machine. Cooperative heartbeat protocol (1s client ping, 2s timeout, 5s drain grace).
- RRF fusion across embedded + team server via
RRFDatabasewrapper. Writes go to primary (embedded) only; reads fan out to both backends and fuse ranked lists using Reciprocal Rank Fusion. Async dual-write to team (Q3). - CLI commands:
memini-ai init,memini-ai status,memini-ai stop,memini-ai migrate. - 4 new env vars:
MEMINI_VECTOR_BACKEND,MEMINI_PGEMBED_DATA_DIR,MEMINI_TEAM_DB_URL,MEMINI_FUSION_MODE. memini-ai migratescript to copy data from external Postgres to embedded.
Migrating from v0.8.2¶
If you have MEMINI_DB_URL set to an external Postgres:
- Easiest: Add
export MEMINI_VECTOR_BACKEND=postgres-externalto your shell. No data migration needed; behavior identical to v0.8.2. - Switch to embedded:
unset MEMINI_DB_URLthenmemini-ai migrate --from='<your old MEMINI_DB_URL>'. Your data is copied to the embedded server; the source DB is untouched. - Both (RRF fusion): Set
MEMINI_TEAM_DB_URLto your team server andMEMINI_FUSION_MODE=rrf. Embedded handles local writes; team handles shared knowledge; queries fuse both.
[0.8.1] - 2026-07-13¶
Fixed¶
- CI re-trigger for memini-vision dependency — The v0.8.0 CI run failed at "Sync dependencies" because the new
[vision]optional dependencymemini-vision>=0.1.0was not yet published to PyPI. This release exists to re-run the publish workflow after memini-vision v0.1.1 became available.
Notes¶
- No code changes from v0.8.0. The v0.8.0 source is unchanged. This release is purely a CI re-trigger.
- Original v0.8.0 tag preserved on origin as a historical record of the failed publish attempt (CI failed at
uv syncbecausememini-vision>=0.1.0was unresolvable). v0.8.1 is the first real release. - Quality gates unchanged: 812 + 13 tests pass, ruff clean, mypy clean.
[0.8.0] - 2026-07-13¶
Added¶
- Image Recall RRF fan-out arm — when
MEMINI_IMAGE_SEARCH_ENABLED=true,query_memoriesnow adds a third RRF fan-out arm that callsmemini-vision.ImageQuery.search_by_text(CLIP text tower over thememories_imagetable) and fuses the result with the existing 384-dim MiniLM and 1024-dim BGE-M3 ranked lists via the unchangedreciprocal_rank_fusion()(k=60). A memory that appears in both text and image lists gets both contributions summed — the natural boost for multi-modal agreement. The image arm is best-effort: any CLIP failure (model download, DB error) is caught, logged, and the text RRF proceeds with 2 lists instead of 3. Implementation:_query_dual_model_rrfrenamed to_query_multi_model_rrf(it now handles 2 OR 3 models); the image arm is the ONLY change, guarded byif get_config().image_search_enabled:. memories_imagetable — new PostgreSQL table (migration000008_add_memories_image.sql) holding 768-dim CLIP image embeddings for memories with associated images (screenshots, diagrams). 1:1 FK tomemories.idwithON DELETE CASCADE.vector(768)accommodates both ViT-B/32 (zero-padded to 768) and ViT-L/14 (native 768). Shared withvidere-mcpvia thememini-visionlibrary — the table is created at memini-ai startup regardless of whether image search is enabled, so videre-mcp can write image rows without memini-ai needing image search on. Idempotent migration (CREATE TABLE IF NOT EXISTSeverywhere — safe to re-run).source_type='image'— thememories.source_typeCHECK constraint is extended to include'image'(a superset of the existing constraint; existing rows still satisfy it).- 5 new config fields —
image_search_enabled(defaultfalse),image_clip_model(clip-ViT-B-32orclip-ViT-L-14),image_clip_device(auto/cpu/cuda),image_dir(~/.memini-ai/images),image_db_url(empty → falls back todb_url). All use theMEMINI_IMAGE_*env var prefix. Two validators enforce validimage_clip_modelandimage_clip_devicevalues. [vision]optional dependency —pyproject.tomlgainsvision = ["memini-vision>=0.1.0"]. Users who don't install it see no change; thememini_visionimport is lazy (only happens inside theif image_search_enabled:block).
Backwards Compatibility¶
- Text-only users see zero behavior change. When
MEMINI_IMAGE_SEARCH_ENABLEDis unset orfalse(the default), no CLIP model loads, no image table is queried, thememini_visionimport never happens, and_query_multi_model_rrfis byte-for-byte identical to the v0.7.9_query_dual_model_rrf. Verified by re-running the existing RRF tests. - The
memories_imagetable is created at startup even when image search is off (empty + unqueried), ensuring videre-mcp can write to it without coordination.
New Environment Variables¶
| Variable | Default | Description |
|---|---|---|
MEMINI_IMAGE_SEARCH_ENABLED | false | Master gate for the image recall RRF arm. |
MEMINI_IMAGE_CLIP_MODEL | clip-ViT-B-32 | CLIP model (clip-ViT-B-32 or clip-ViT-L-14). |
MEMINI_IMAGE_CLIP_DEVICE | auto | Device (auto/cpu/cuda). |
MEMINI_IMAGE_DIR | ~/.memini-ai/images | Filesystem directory for stored images. |
MEMINI_IMAGE_DB_URL | (empty → MEMINI_DB_URL) | PostgreSQL URL for the image index. |
Quality Gates¶
- 799 tests pass, 3 skipped, 0 new failures (10 pre-existing failures from Keras 3 / tf-keras environment incompatibility, documented since v0.7.9).
- ruff 0 errors
- mypy: 1 pre-existing numpy stub error on Python 3.14 (not from this work)