Agents get lighter ways in: a 3-tool MCP server with Palace Query Language, shared-brain rules for host:harness:project identities, and optional native exact-vector search. New installs follow XDG, and storage fixes close a silent write-loss path, a macOS integrity refusal and several hangs.
Upgrade notes
mempalace rulesno longer accepts--agent. Pass--host,--harnessand--project, then re-render any installed shared-brain block. (#2508)get_collection()refuses names MemPalace never reads. Any name other than the configured drawers collection ormempalace_closetsraisesCollectionNameMismatchError; maintenance scripts can pass_skip_name_check=True. (#2446)- The MCP event listing returns the newest events first when no cursor is given.
mempalace_event_listandpalace_coordinatenow default to newest-first unlesssince_event_idororderis passed;mempalace logstream listandLogstream.list_events()still default to oldest-first. (#2497) - New installs keep config and palace under
~/.config/mempalace(or$XDG_CONFIG_HOME/mempalace). Existing~/.mempalaceinstalls are unchanged. (#148)
Performance & Architecture (Dual-Track Rust Engine)
-
Optional native Rust exact-vector acceleration (
rust_exact).
Reads the existing SQLite format into a contiguous float buffer, uses Rayon
for large scans, and releases the Python GIL. Local and external writes
invalidate the native cache. Loading is collection-scoped and decodes blobs
safely; serial and parallel scans preserve deterministic tie ordering.
Complex filters and absent native extensions fall back to Python. -
Standalone native vector CLI and optional wheels.
GitHub releases include native wheels and platform executables. The CLI
accepts JSON embedding vectors via--vectoror stdin; it does not embed
text or require Python. Platform runtime libraries may still be required. -
NumPy top-k optimization.
Cache vector norms and partition distances before sorting the selected
results, preserving row-order ties at the cutoff. -
Read-only search operations stop contending with palace writer leases.
searcher.pyand_open_search_collectionnow explicitly open collections withread_only=True. Read-only queries connect in SQLite WAL reader mode without attempting schema initialization or writer lease locks, allowing CLI and agent searches to run concurrently with an active background MCP server or writer process without raisingMineAlreadyRunning.
Refactors
- MCP server is a package, not an 8 000-line module.
mempalace.mcp_serverstays the public import path (TOOLS,handle_request,main, everytool_*handler, and the process-global names tests monkeypatch). Implementation lives in domain files undermempalace/mcp_server/so PRs can target drawers, KG, coordination, protocol, or HTTP without colliding on one god-file. Fragments exec into the package namespace — a physical split, not a behaviour change. - CLI is a package, not a 4 000-line module.
mempalace.clistays the public import path (main, everycmd_*handler, and the names tests monkeypatch). Implementation lives in domain files undermempalace/cli/so PRs can target init, mine, search, repair, logstream, or argparse without colliding on one god-file. Fragments exec into the package namespace — a physical split, not a behaviour change. - MCP server tests are split by domain.
tests/test_mcp_server.pyis nowtests/mcp/(protocol, read, search, write, diary, KG, guards, stale-library) so community MCP PRs do not collide on one 8 600-line test file. Shared helpers live intests/_mcp_server_helpers.py. - Searcher is a package, not a 2 200-line module.
mempalace.searcherstays the public import path (search,search_memories,SearchError, and the helpers tests monkeypatch). Implementation lives undermempalace/searcher/(ranking, filters, sqlite BM25, candidates, query, CLI, render) so PRs can target one seam. Fragments exec into the package namespace — a physical split, not a behaviour change. - Palace ops are a package, not a 1 800-line module.
mempalace.palacestays the public import path (get_collection,mine_lock,mine_palace_lock, closet helpers, and the names tests monkeypatch). Implementation lives undermempalace/palace/(collection, backend, closets, mine locks, mined-set) so PRs can target one seam. Fragments exec into the package namespace — a physical split, not a behaviour change.
Features
- Shared-brain rules use a
host:harness:projectidentity and can name either MCP tool surface.mempalace rulestakes--host,--harnessand--project(stable lowercase tokens) instead of--agent, plus--mcp full|light:full(default) names the 45-toolmempalace-mcptools, andlightnames thepalace_query/palace_exec/palace_coordinatetriad with identical prose. The rendered block has agents compose the identity from their current workspace, sweep the inbox when they start collaborating instead of arming a watcher at session start, and armlogstream watchonly when asked to listen, after claiming a task or after delegating.logstream watch --agentnow defaults--state-fileto~/.mempalace/watch/<agent>.jsonwith_doubled and:sanitized to_, so Windowshost:harness:projectidentities need no private path. (#2508) mempalace-light-mcp: a 3-tool MCP server with Palace Query Language.palace_query,palace_execandpalace_coordinatecover search, taxonomy, KG, graph, diary, drawers, mining, tunnels and RFC 003 coordination through a one-line PQL string (FIND "oauth" IN backend/auth LIMIT 5) or a structured dict, at about a quarter of the 45-tool schema (10.6 KB versus 41.6 KB). Writes go through the same sanitizing handlers, so drawer text stays verbatim; search ranking is unchanged, stdio is hub-first, and--read-onlyrefusespalace_execandpalace_coordinate. Register it beside the full server rather than in place of it:claude mcp add mempalace-light -- mempalace-light-mcp. (#2412)- Config follows the XDG Base Directory Specification on new installs. Resolution order is
$MEMPALACE_CONFIG_DIR, then an existing~/.mempalacethat holdsconfig.json,people_map.jsonorpalace/chroma.sqlite3, then$XDG_CONFIG_HOME/mempalace(absolute values only), then~/.config/mempalace. Existing installs keep~/.mempalace, andpalace_pathdefaults to<config_dir>/palace. (#148) - CLI writes honour the daemon write-routing policy.
mine,sweep,syncand the post-setup mine ininitfollowdirect/prefer/requirefromMEMPALACE_CLI_WRITE_ROUTINGorwrite_routing.cliinconfig.json, with--daemon/--directas per-command overrides. The default staysdirect, so nothing changes unless you opt in. Once a job is submitted to the daemon it is never re-run directly on error, since the daemon may already have accepted it.sweepis now a daemon job. (#2033) - pgvector fleets can share one table set across machines. The opt-in
pgvector_shared_namespace(config key,MEMPALACE_PGVECTOR_SHARED_NAMESPACE, or theshared_namespacebackend option) replaces the palace-path hash in the table prefix, so nodes with different local palace paths read and write the same tables. Unset, prefixes are byte-for-byte unchanged; the value is normalized strictly and recorded in the backend marker. (#2167) - Hybrid-rank weights are configurable.
MEMPALACE_HYBRID_VECTOR_WEIGHT/MEMPALACE_HYBRID_BM25_WEIGHT, orhybrid_rank_vector_weight/hybrid_rank_bm25_weightinconfig.json. Defaults stay 0.6 / 0.4, and malformed values fall back to them. (#2430) - Drawers track
last_modified. New drawers set it fromfiled_at; everyupdate_drawerthat changes content, wing or room stamps it afresh whilefiled_atkeeps the creation time. Legacy drawers reportfiled_atas theirlast_modified. (#2147) - Search results say where their dates come from. Vector, union and lexical-only hits return
filed_at,authored_at_source,content_dateandcontent_date_source(filename,frontmatter,body,mtime, orunknown). Provenance is recorded at mine time and never reconstructed for legacy drawers. (#2453) mempalace_kg_timelinepaginates.limit/offsetfollow thelist_drawersconvention; the hardcoded first-100 cap is gone. (#2406)- Logstream listings default to the newest events when no cursor is given.
mempalace_event_listandpalace_coordinatereturn the most recent events first whenorderis omitted and nosince_event_idis passed, and keep ascending order when resuming from a cursor; an explicitorderalways wins and is case-insensitive. The light server'spalace_coordinateschema now exposesorder,limit,previewandsince_created_at. (#2497) dynamics.effective_strength()computes connection decay at read time. It is pure, so reading a strength cannot change it and repeated maintenance passes cannot compound decay the wayapply_decaydoes. The module docstring no longer claims hallways and tunnels already potentiate or decay. (#2416)- MemPalace plugin for the DeepSeek Harness (
.dsh-plugin/). A DSH bundle plugin with three rows, built against the DSH 0.1.5 extension points.mempalace-recallputsmempalace wake-upfor the project's wing in each top-level session's system prompt from its first model call; DSH resolves prompt text before plugins can wait on anything else, so the row holds only the first assembly, for a bounded time, and never fails the step.mempalace-autosavekeeps an append-only transcript per session under$DSH_HOME/mempalace/transcripts/, holding only what the user wrote and the assistant's text (never harness context, tool results, reasoning or compaction summaries), and runs MemPalace's ownstop,precompactandsession-endhooks against it through the newdshharness.mempalace-mcpserves the palace through DSH's own MCP client withmempalace-light-mcp: its 3 tools cost about 2,100 schema tokens per model step against about 7,700 for the 45-tool server. Anode --testsuite covers the plugin andtests/test_dsh_plugin.pyruns it under pytest.
Improvements
repairsays when it declines an in-place FTS5 heal, and names the SQLite that decided. Declining was a silentreturnon two paths, so the operator saw the ABORT banner with no sign that a rebuild had been considered;--dry-runwithheld the same explanation. All of them now print it, and the--dry-runcopy is marked as a preview so it cannot be read as the run it is predicting.sqlite3.sqlite_versionaccompanies every verdict these paths state — the abort banner, both--dry-runbranches, the heal's outcome line, the post-recovery check, all three shapes of the MCP integrity payload, the tool-refusal payload, the integrity log line andMineValidationError's message — because a clean result from a build that cannot detect a given FTS5 fault reads exactly like one from a build that can. The post-mine validator reports atwarningrather thaninfo, the level that survives on themempalace minepath, which configures no logging. (#2240, #2254)- A skipped startup integrity probe is no longer reported as a clean verdict. Above
MEMPALACE_STARTUP_INTEGRITY_MAX_MBnoquick_checkruns, yet the MCP payload answeredchecked: true, ok: true. It now returns the not-applicable shape with the skip as itsreason, and that reason states sizes precisely enough to show the comparison it reports — a 1.4 MB database against a 1 MB limit used to read as "is 1 MB, over the 1 MB limit". Write tools gate on recorded errors rather than onok, so nothing is newly refused, and/statuszno longer collapses a missing verdict into a failure —okis false only when a check came back dirty, which also stops a non-chroma backend from reporting itself unhealthy (#1931). (#2240, #2254) - The FTS5 classifier is anchored to the start of the
quick_checkrow.sqlite_integrity_errorsreports a failed probe asPRAGMA quick_check failed: <error>, and an unanchored match read such a row as an isolated FTS5 fault — authorizing a rebuild on a database whose state was never established.fts5: checksum mismatch, which SQLite 3.51.2 emits, stays unmatched deliberately: it does not name which side is damaged, and a rebuild reads from the content table. (#2240, #2254) list_drawersandgraph_statswalk a Qdrant collection in one scroll.BaseCollection.get_all_rows()joinsget_all_metadata()as the ids-carrying bulk read; its default still pages throughget(limit=, offset=), and Qdrant overrides it with a single scroll where the offset loop had cost 275 s and 292 s on a 126k-drawer palace. The embedding wrapper forwards the call so the override is reached. (#2452, #2474)- Hallway recompute reads only the mined wing.
compute_hallways_for_wingfetched every drawer in the palace on each mine; it now pages throughget(where={"wing": ...})in bounded pages, keeping the #1619 variable-limit protection, so the cost follows the wing rather than the palace. On Qdrant, whoseget()still scrollsoffset + limitrows for each page, a wing larger than one page re-reads its earlier pages. (#2466, #2477) - CLI
statusreads backend metadata in one pass when the direct SQLite tally is unavailable. Chroma palaces are still tallied straight fromchroma.sqlite3. On the collection path, used by backends such as Qdrant,status()now callsget_all_metadata()when the collection exposes it instead of paging withget(): 1 m 46 s down to 3.4 s on a 160k-drawer Qdrant palace. (#2153, #2154)
Bug Fixes
- The transcript-path fallback no longer gives every git worktree its own wing.
_wing_from_transcript_path's primary path (readingcwdfrom the JSONL) already collapsed a<project>/.claude/worktrees/<wt>segment before deriving the wing; the fallback path, used whenevercwdis absent, had no equivalent strip, so the flattened--claude-worktrees-<wt>segment survived into the wing name. Applied the same collapse there. (#2388, #2454) mempalace mine --daemonno longer mines the daemon's own working directory instead of the caller's. The daemon is a long-lived background process that keeps whatever cwd it happened to start with, so a relative source (mempalace mine .) submitted to it resolved against that stale cwd rather than the directory the CLI call actually ran from, silently mining the wrong project into the wrong wing on every subsequent hook-driven call.cmd_minenow resolves the source to an absolute path before it enters the daemon job payload, matching the resolution_forward_mine_to_hubalready does for the hub-forwarding path. (#2441, #2467)mempalace initno longer replaces aknown_entities.jsonit could not read with the entities of that one run. The merge reads the registry, falls back to an empty dict when the read does not conclude, and rewrites the file whole, so a registry holding four categories became one holdingpeople: 1, measured, with_load_known_entitiesdropping from 10 names to 1 andget_topics_by_wingfrom two wings to none, while the command printedRegistry updated. None of that needs an interrupted write: through the CLI, onemempalace initreplaced a 28-byte JSON array and a 172-byte truncated object alike, and a registry re-saved in a legacy encoding ended the run in a traceback before any of it. The writer also truncates the file and serializes into it afterwards, so a killed run or a full disk leaves the same kinds of file. Reads now treat onlyFileNotFoundErroras an absent registry. One that does not parse, whether from a truncated write, a byte that is not UTF-8, a hand-edit that lost a brace or a JSON array, is renamed aside and its new name printed before a fresh registry is written, and one that exists but cannot be read is left alone with a message rather than overwritten. The write itself goes through a temporary file and a rename, asmigrate._apply_topics_by_wing_renamesalready did for this same file, so an interrupted run leaves the previous registry byte for byte. A byte that is not UTF-8 also stopped leavingUnicodeDecodeErrorout of the call, wheremempalace initrendered it as a traceback. The call answersNonewhen it wrote nothing, somempalace initno longer printsRegistry updatedover a registry it left alone; a registry reached through a symlink is written through it rather than replaced; a directory that will not take a temporary file gets the merge in place with a message rather than a traceback; and a UTF-8 byte-order mark is no longer read as a parse failure. (#2358, #2359)repair-statusno longer reports a database it could not reach as one the palace never had.repair.statusdrew that line withos.path.isfile, which follows symlinks and foldsOSErrorandValueErroralike into False, soPalace dir at … exists but has no chroma.sqlite3 yetandstatus: "uninitialized"came back for a palace whose database sat intact behind a directory permission, for one whose name had become a broken symlink, and for a symlink loop — word for word what a palace that really has no database is told. What that sentence invites is a re-mine, which on the broken-symlink shape builds a second database beside the untouched first and leaves behind any drawer that has no source file to be re-derived from. Absence is now decided byENOENTalone, the_integrity_target_is_absentpredicate #2290 added, and an unreachable database no longer takes that early return: the capacity report below it answers for the palace instead, printingsqlite count: (unreadable)andUNKNOWN. A truly absent database still reportsuninitialized. Getting past that gate puts one more state in front of the read: a named pipe, whichos.path.isfileused to turn away and whose open parks in the kernel until a writer arrives — inside sqlite3, where theO_NONBLOCKanswerminer._read_text_no_followuses is not available. What keeps the read from parking is a type check next to the open itself, insqlite_drawer_count; the gate above repeats it only so the operator is told which state this is instead of the(unreadable)a permission also produces, and a check at the gate alone would not do, because under a directory this process cannot enter itsstatfails and answers False by design. No other type needs a case: a directory, a socket, a block device and a character device each answered with an error in well under a second, and landed in that same unreadable report. The printed answer and the returned dict are what change; the exit code was 0 for all of these before and still is. (#2293, #2393)- Layer 1 wake-up fetches the most recent drawers instead of an arbitrary scan window.
BaseCollection.get_recent()returns the newest N byfiled_at; pgvector overrides it with anORDER BY ... DESC LIMIT npushdown (capability tokensupports_recency_order), and backends without pushdown keep the previous scan-and-sort behavior. On palaces larger than the 2,000-drawer scan cap, wake-up no longer leads with the oldest backfill. Ordering is on the storedfiled_attext, the same comparison Layer 1 has used since #1630. (#1630, #2168) - Checkpoints summarize the conversation, not the harness. Text the tooling injects with
role: "user"(slash-command expansion, local-command blocks, task notifications, skill preambles, pasted-image placeholders, interruption records) no longer becomes the checkpoint'srecent:line. The match is anchored to the opening of the message, so a genuine message that quotes one of those wrappers anywhere after the first character is still summarized. (#2170) - The backend header sniff no longer drops the WAL writer's POSIX locks.
detect()and_database_signature()opened the palace database with a plainopen()on nearly every MCP call; on POSIX, closing that descriptor released everyfcntllock the process held on the file, including the WAL writer's SHARED lock, so an external connection's clean close could checkpoint and unlink the live-wal/-shmfrom under it. The sniff no longer disturbs the process's locks. (#2471, #2472) - macOS no longer refuses every tool on a healthy WAL palace. Apple's SQLite fails a read-only connection's first statement when the
-wal/-shmsidecars are absent, which is the normal state before any process opens the palace, so the integrity gate reported the palace unreadable. That one case now takes a normal open; everything else keepsmode=ro. (#2489, #2490) - A malformed JSON-RPC request that carries an
idgets an answer instead of silence. A non-stringmethodor non-objectparamsraised inside dispatch, so stdio wrote nothing and HTTP closed the socket, leaving the client waiting on thatidforever. Malformed envelopes now return-32601/-32602, and a dispatch failure returns-32603.notifications/*methods are still never answered, as before. (#2231, #2232) get_collectionrefuses a collection name MemPalace never reads. Any name other than the configured drawers collection andmempalace_closetsraisesCollectionNameMismatchError(aValueError) instead of silently creating an orphan store that search, MCP and repair cannot see. Maintenance scripts can pass_skip_name_check=True. (#2347, #2446)- A stranded HNSW index is no longer reported as flush-lag. When
index_metadata.pickleis absent, two states that never resolve on their own are now named. Ahnsw:sync_thresholdthe collection can never reach is reported asflush_unreachable, with how to force a flush, and vector search stays enabled. A replacement stub whose payload cannot hold the SQLite count is reported asdiverged, withmempalace repairas the fix. Both need at least 2,000 embeddings (the stub case, a shortfall of at least 2,000), so smaller collections still get the flush-lag report. (#2435) - Impossible counts in an HNSW
header.binare rejected. Astronomical capacities and element counts above capacity now feed segment health, startup quarantine and the capacity report before Chroma's native reader opens the segment. (#2146) - Caller-vector Chroma collections are created without an embedding function. Chroma attached
DefaultEmbeddingFunctionwhen none was passed and persisted it in the schema, so a document-only write on a 3-dimensional collection tried to embed at 384 dimensions. Collections are now created and reopened EF-free, and fail closed if one is still exposed. (#2141) - Chroma systems owned by the backend are stopped before the shared cache is cleared. On an external inode or mtime change the backend cleared Chroma's process-global system cache before closing its own clients, so the displaced
System.stop()never ran. Clients the backend does not own, such as a directly constructedPersistentClient, are outside this fix. (#2421) - pgvector reconnects once when Postgres drops the pooled connection. A server restart used to cost each long-lived client one
terminating connection due to administrator commanderror. SQLSTATE class 57 and a closed handle retry once on a fresh connection; statement-level errors still propagate on the first attempt. (#2084) - CLI
search --wing/--roomrecovers from Chroma'sError finding id. It now uses the same filtered-query fallback as MCP search. (#2373, #2380) - An unparsed Codex rollout is no longer imported as raw JSON. A rollout with
session_metathat the installed parser cannot normalize is skipped with a logged reason and left eligible for a retry after a parser update; existing drawers are untouched. (#2469) mempalace_diary_readsees entries past the first 10,000. It pages through the agent's diary and keeps the newestlast_n, andtotalis no longer capped. (#563, #2464)- A backend that fails to initialize is no longer reported as a peer writer. Setup failures are reported separately from lease contention, with recovery guidance, and the light MCP client forwards writes to a live hub instead of first trying to take the local writer lease. (#2465)
- A busy daemon keeps its registration.
start_daemondeletedendpoint.jsonand the pid file after one failed health probe, stranding a live daemon that still owned the palace lock. (#2442, #2473) - Stripping a leaked
PYTHONPATHkeeps the running interpreter's own site-packages. An embedding host that put the venv's ownpurelib/platlibonPYTHONPATHlost its dependencies at import. (#2484, #2496) - A named pipe at
chroma.sqlite3no longer stops the MCP server from answering. The integrity probe opened the pipe for reading, which waits until a writer arrives, and gated tool calls,mempalace_status,mempalace_reconnectand/statuszall run it or wait behind it. Over stdio the first such call left the session answering nothing,pingincluded, until the idle watchdog ended the process; over HTTP those calls hung whilepingand/healthzkept answering. The probe now refuses the pipe by type before any open, asrepair-statusalready does, and reports it as an integrity error: gated tool calls get the-32002refusal naming it, andpython -m mempalace.repair rebuildstops at the abort banner instead of hanging. (#2498) - Hooks run on fresh installs that keep their config under
~/.config/mempalace. Since #148 a new install writes its config and palace to the XDG config directory and never creates~/.mempalace, but the hooks' kill-switch only looked for~/.mempalace, so the Stop, PreCompact and SessionEnd hooks returned{}without saving anything: a freshmempalace initfollowed by a PreCompact hook filed 0 drawers, against 20 once~/.mempalaceexisted. The kill-switch inhooks_cliand in the Antigravity shell hooks now also accepts the resolved config directory, and the shell hooks readhooks.auto_savefrom it. Existing installs behave exactly as before. mempalace wake-upno longer reports a healthy palace as missing while a hub is running. The L1–L3 layers opened the palace writable, and on thesqlite_exactbackend a writable open takes the palace mine lock to initialise its schema. With a hub, daemon or mine holding that lock the open failed, and the swallowed exception printed "No palace found. Run: mempalace mine ", inviting a pointless re-mine. The layers now ask for a read-only open, whichsqlite_exactserves without the lock; ChromaDB ignores the option, but its collection open never took the mine lock. A lock conflict that does surface gets its own message instead of the missing-palace one.- A writable
mempalace servewaits for a busy writer lease instead of restart-looping. Startup refused with status2the moment another process held the palace writer lease, and under the systemd template'sRestart=alwaysthat became a loop that never started, never gave up and never reachedfailed. The server now retries with backoff forMEMPALACE_MCP_WRITER_WAIT_SECONDS(default120,0refuses at once) and logs once when the wait starts; a backend or lock-directory failure still refuses immediately. The unit setsRestartPreventExitStatus=2, so a wait that runs out lands infailedwith the reason in the journal, and the idle watchdog runs the registered cleanup before exiting, soserverinfo.jsonno longer advertises a dead PID. (#2500, #2501) mempalace daemon startandstoprecover from a stale registration. The #2442 guard refused to start while the registered pid was alive, and after a crash that pid can belong to an unrelated process, so start refused forever; the suggestedmempalace daemon stopused the same failing health probe and did nothing. A registered pid that is dead, or whose process started after the registration was written, is now treated as stale:startreplaces it andstopremoves it. A live daemon that does not answer keeps its registration, a foreground start refuses beside it as a background one does, and both commands name the process to stop. (#2522)
Documentation
- Codex plugin install instructions use the built-in marketplace. (#2372)
Install / upgrade
pip install --upgrade mempalace # or: uv tool upgrade mempalace / pipx upgrade mempalaceOptional native exact-vector search: the mempalace_native_core wheels and standalone mempalace-native executables are attached to this release (see crates/README.md).
A big thank-you to everyone who contributed. This release includes 63 merged PRs: 60 from 20 contributors, twelve of them first-time, plus three Dependabot bumps.
Install: pip install -U mempalace==3.10.0
What's Changed
- feat(mcp): Lightweight MCP Server with Palace Query Language (PQL) & Principled Retrieval Engine by @igorls in #2412
- feat: add optional Rust exact-search backend and native CLI by @igorls in #2444
- fix(mcp): distinguish writer setup failures and forward writes to hub by @igorls in #2465
- fix(daemon): keep a live daemon's registration when the health probe fails by @L4XB in #2473
- fix: paginate diary reads past 10k entries by @Formatted in #2464
- refactor(mcp): split mcp_server.py into a package by @igorls in #2485
- refactor(cli): split cli.py into a package by @igorls in #2486
- chore(deps-dev): bump ruff from 0.16.1 to 0.16.6 by @dependabot[bot] in #2483
- chore(deps): bump actions/download-artifact from 4 to 8 by @dependabot[bot] in #2482
- chore(deps): bump actions/upload-artifact from 4 to 7 by @dependabot[bot] in #2481
- ci: sync ruff pins in ci.yml and pre-commit with pyproject.toml (0.16.6) by @igorls in #2487
- perf(hallways): scope the drawer fetch to the wing instead of walking the palace by @L4XB in #2477
- fix(hooks): collapse worktree segment in transcript-path wing fallback by @AmirF194 in #2454
- test(mcp): split test_mcp_server.py into domain files by @igorls in #2488
- ci: pin GitHub Actions to immutable commit SHAs for CI/CD best practices by @vibemasshq-dev in #2479
- fix: reject raw fallback for unparsed Codex rollouts by @jdorado in #2469
- test(init): make leaked-PYTHONPATH test layout-agnostic (#2460) by @BuboTheWise in #2461
- test(config): skip EPERM rewrite test on Windows by @BuboTheWise in #2455
- fix(ci): isolate MCP writer-lock state between tests by @fatkobra in #2422
- fix(chroma): release systems before cache reset by @fatkobra in #2421
- fix(repair): prove an absent database by ENOENT, not by isfile (#2293) by @mvalentsev in #2393
- fix(search): reuse filtered-query fallback in CLI by @MichaelWave369 in #2380
- fix(miner): keep a known_entities.json the merge could not read (#2358) by @mvalentsev in #2359
- fix(sqlite): read a WAL palace whose sidecars are gone by @L4XB in #2490
- fix(mcp): answer every JSON-RPC request that carries an id (#2231) by @mvalentsev in #2232
- fix(hooks): skip harness boilerplate in the checkpoint recent: line by @ATKabli in #2170
- feat(drawers): track last_modified timestamps by @fatkobra in #2147
- fix(repair): anchor the FTS5 classifier, attribute integrity verdicts (#2240) by @mvalentsev in #2254
- fix(palace): fail loudly on a mismatched get_collection name (fixes #2347) by @BuboTheWise in #2446
- feat: make hybrid-rank vector/BM25 weights configurable (#2298) by @BuboTheWise in #2430
- feat(pgvector): shared namespace for multi-node fleets on one database by @ATKabli in #2167
- fix(chroma): reject impossible HNSW header counts by @fatkobra in #2146
- refactor: remove seven unused helpers by @Vinv-AI in #2386
- refactor(search): split searcher.py into a package by @igorls in #2492
- fix(chroma): persist EF-free caller-vector schema by @fatkobra in #2141
- fix(layers): fetch the most recent drawers for L1 wake-up by @ATKabli in #2168
- fix(pgvector): reconnect once when the server drops the pooled connection by @messelink in #2084
- fix(hnsw): detect stranded indexes instead of reporting them as flush-lag by @Vanguadm in #2435
- feat(dynamics): add read-time effective_strength, and correct the module docstring by @scotmcc in #2416
- docs(plugins): Revise README for MemPalace Codex plugin installation by @tanji in #2372
- fix(status): wire CLI status() to get_all_metadata() fast path (#2153) by @javapacr in #2154
- fix: expose content-date provenance in search results by @jason-allen-oneal in #2453
- perf(qdrant): walk the cursor once for list_drawers and graph_stats by @L4XB in #2474
- test(logstream): assert the idle deadline caps the poll without a clock by @L4XB in #2491
- refactor(palace): split palace.py into a package by @igorls in #2494
- fix(mcp): stop peer sync thread and bind logstream path to avoid test flake by @igorls in #2495
- fix(mine): resolve relative daemon source against the caller's cwd by @AmirF194 in #2467
- feat(kg): paginate kg_timeline with limit/offset (list_drawers convention) by @raman325 in #2406
- feat: support XDG Base Directory Specification (closes #46) by @mvalentsev in #148
- feat(cli): apply shared daemon write-routing policy by @fatkobra in #2033
- fix(init): keep this environment's own paths when stripping leaked PYTHONPATH by @L4XB in #2496
- fix(storage): stop header sniff from dropping the writer's POSIX locks by @felixmurphybox-dot in #2472
- feat(coord): add context-aware ordering and expose pagination parameters in light MCP by @igorls in #2497
- fix(repair): refuse a named pipe in the integrity probe (#2498) by @mvalentsev in #2499
- fix(hooks): let the kill-switch accept the XDG config directory by @igorls in #2504
- docs(changelog): consolidate unreleased entries for the next release by @igorls in #2503
- feat(dsh): add the MemPalace plugin for the DeepSeek Harness by @igorls in #2505
- feat(rules): host:harness:project identity and MCP-shape aware shared-brain rules by @igorls in #2508
- fix(hooks): expand a leading ~ in the shell config-dir resolver by @igorls in #2509
- test(storage): wait for the writer lock to release after close by @igorls in #2512
- fix(mcp): run the registered cleanup when the idle watchdog exits by @L4XB in #2501
- fix(mcp): wait for a busy writer lease before refusing HTTP startup by @igorls in #2516
- chore(release): 3.10.0 by @igorls in #2518
- fix(daemon): recover start and stop from a stale registration by @igorls in #2522
- chore: sync main into develop before the v3.10.0 promotion by @igorls in #2523
- release: promote v3.10.0 to main by @igorls in #2519
New Contributors
- @L4XB made their first contribution in #2473
- @Formatted made their first contribution in #2464
- @vibemasshq-dev made their first contribution in #2479
- @jdorado made their first contribution in #2469
- @BuboTheWise made their first contribution in #2461
- @Vinv-AI made their first contribution in #2386
- @Vanguadm made their first contribution in #2435
- @scotmcc made their first contribution in #2416
- @tanji made their first contribution in #2372
- @javapacr made their first contribution in #2154
- @jason-allen-oneal made their first contribution in #2453
- @felixmurphybox-dot made their first contribution in #2472
Full Changelog: v3.9.0...v3.10.0