Conversation
The openclaw skill was last updated when mempalace exposed 19 MCP tools. Since then 13 more agent-facing tools have landed; this PR documents the 8 that openclaw should expose so agents can call them natively instead of falling back to `npx mcporter call ...`: Search & Browse: - mempalace_list_drawers (paginated drawer listing) - mempalace_get_drawer (fetch a single drawer by id) Palace Graph: - mempalace_create_tunnel (explicit cross-wing link) - mempalace_list_tunnels (enumerate explicit tunnels) - mempalace_delete_tunnel (remove an explicit tunnel) - mempalace_follow_tunnels (walk explicit tunnels from a room) Write / Session: - mempalace_update_drawer (mutate content or relocate a drawer) - mempalace_memories_filed_away (ack the silent auto-save hook) The 3 admin-only tools (mempalace_sync, mempalace_hook_settings, mempalace_reconnect) are intentionally left out — they're host/admin operations, not agent-facing memory operations. The Hermes MemoryProvider plugin landing in #1684 makes the same call. Version bumped 3.3.0 -> 3.4.0 (additive tool surface, no breaking changes to existing tool docs).
- Fix mempalace_find_tunnels params: (required) -> optional. The MCP handler defaults both wing_a and wing_b to None (mempalace/mcp_server.py:1277), so the prior docs were factually wrong. Caught by gemini-code-assist on PR #1719. - Clarify implicit-vs-explicit tunnel distinction with consistent casing and a brief in-line definition (implicit = discovered from drawer content overlap; explicit = user/agent-declared link). Suggested by copilot-pull-request-reviewer. - Split the mempalace_memories_filed_away one-liner into a short description plus 'Returns' and 'When to call' sub-bullets for readability. Suggested by copilot-pull-request-reviewer.
- ruff format llm_client.py and miner.py (lint job) - _copy_file_no_follow: close src fd if the dst open fails (no leak), and route the rebuild restore through it so backup + restore share one no-follow/regular-file path - update repair tests to assert the unified hardened copy instead of the removed shutil.copy2 calls; backup paths are now timestamped - update normalize large-file test to stub fstat (size is checked on the open fd, not via a pre-open os.path.getsize)
fix: tighten local guards and file handling
…aths The write-ahead log gained its own module in v3.5.0 but sat at 82% coverage; the uncovered lines were exactly the failure/guard branches that uphold its contracts: the cache-hit early return, the restricted-FS chmod/mkdir swallow paths, and the promise that a WAL write failure is logged and never crashes the calling tool. Add five tests covering those branches plus the non-string redaction marker, bringing mempalace/wal.py to 100% and locking the crash-safety guarantees against regression. Test-only; no production change.
Both miner.py and convo_miner.py silently skip files larger than the 10 MB limit with a bare continue. This is especially painful for conversation mining where long Claude/ChatGPT exports routinely exceed 10 MB and vanish with no trace. Print a SKIP warning per oversized file, matching the existing format in split_mega_files.py.
The original commit printed SKIP for oversized files to stdout but the sibling SKIP for symlinks in the same scan_project / scan_convos already went to stderr. Align the new line with that convention. Also adds a SKIP-with-error log for the except OSError arm right below the size check. Files whose stat() raises (permission denied, racing delete, broken symlink that survived the earlier is_symlink check) were the same bug class as the silent oversize drop. Tests switched from captured.out to .err and tightened to the full template; new test covers the OSError arm via a selective Path.stat monkeypatch with a follow_symlinks gate for Python 3.10+.
test(wal): cover WAL crash-safety, idempotent setup, and redaction edge paths
…1857) daemon.py:_detached_kwargs was the last production spawn site still using DETACHED_PROCESS. Swap it to CREATE_NO_WINDOW, matching the hook miner's _detached_popen_kwargs fixed in #1848 — the dedicated follow-up the review bot asked for. `grep -rn DETACHED_PROCESS mempalace/` now returns zero production hits. Survivability is unchanged: CREATE_BREAKAWAY_FROM_JOB (escapes the parent Job Object's kill-on-close) plus the daemon never being attached to the launching console carry survive-terminal-close; CREATE_NEW_PROCESS_GROUP (also kept) isolates Ctrl-C/Break. CREATE_NO_WINDOW is ignored when OR'd with DETACHED_PROCESS, so this replaces the flag rather than adding it. The daemon already redirects stdout/stderr to daemon.log and reads no stdin, so it needs no console. Adds the first tests for _detached_kwargs (posix + windows, cross-platform monkeypatch of the Windows-only flag constants, mirroring the #1848 hooks_cli tests).
* fix: sanitize wing slug for project dirs with special characters Project folders containing characters outside sanitize_name's set (e.g. a leading '+') leaked into the derived wing name, producing names like 'wing_+project' that config.sanitize_name rejects, silently breaking diary auto-save for that project. Add _safe_wing_slug(): collapse non-word runs to '_', trim, and fall back to 'sessions' when a name reduces to nothing. Route the three wing-derivation sites through it. Tests: unit cases for the helper plus a hypothesis property test asserting wing_<slug> always passes sanitize_name for any input. * fix: preserve dots and apostrophes in wing slug for backward compatibility The first pass collapsed every non-word character (including dot and apostrophe) to underscore, renaming existing valid wings — e.g. my.app became wing_my_app — which would orphan diary entries already filed under the old name. Keep dot and apostrophe (both accepted by sanitize_name), collapse consecutive dots to avoid the path-traversal rejection, and trim edge separators. Add backward-compatibility tests for previously-valid names plus a double-dot collapse test. * fix: cap wing slug length to stay within sanitize_name's limit sanitize_name rejects names over 128 characters, so a very long project directory name would produce a wing name that fails validation, re-triggering the silent auto-save break this PR fixes. Truncate the slug to 120 chars (the wing_ prefix keeps the total under 128). Widen the hypothesis property test to max_size=300 so it exercises the length path, and add an explicit truncation test. Addresses gemini-code-assist review feedback on PR #1852. --------- Co-authored-by: Ivan Antsimonau <ivan.antsimonau@katim.com>
…ath (#1863) The non-daemon synchronous mine fallback in _mine_sync() spawned the mine subprocess without CREATE_NO_WINDOW, flashing a visible console window on every PreCompact fire on Windows. The async paths (_spawn_mine, _desktop_toast) already pass it via _detached_popen_kwargs(); this sync path was missed. getattr(..., 0) is a no-op off-Windows. Fixes #1862 Co-authored-by: David Finkelstein <david@finkelstein.us>
…aceConfig.palace_path correctly called os.path.expanduser() for\nenv-var paths but not for paths read from config.json. If config.json\nstores palace_path as '~/.mempalace/palace' (the default written by\ninit), the tilde was returned unexpanded.\n\nDownstream callers such as cli.py cmd_mine did call expanduser when\n--palace was passed explicitly, but fell through to MempalaceConfig()\nwhen no flag was given, inheriting the unexpanded string. Python's\nos.makedirs and chromadb.PersistentClient treat a leading tilde as a\nliteral directory name rather than the home directory, so the palace\nwas silently written to a CWD-relative path such as\nmy_project/~/.mempalace/palace.\n\nThe fix is a single os.path.expanduser() call on line 343 of\nconfig.py, mirroring the existing env-var branch on line 342. Since\nDEFAULT_PALACE_PATH is already expanded at module load (line 197),\nexpanduser on an absolute path is a no-op, so the default case is\nunaffected.\n\nSymptoms: scattered {project}/~/.mempalace/palace directories, palace\nalways appears empty after mine, search returns Collection does not\nexist, launchd-driven nightly mine writes to a different location than\ninteractive mine.\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>n (#1865)
An empty link_lists.bin is not corruption on its own: hnswlib stores the layer-0 graph inside data_level0.bin and only writes link_lists.bin for elements promoted to level > 0. A small/low-fanout index where every element stays on layer 0 serializes an empty link_lists.bin and loads fine. Flagging that shape as corrupt produced a self-perpetuating quarantine loop — repair rebuilt the byte-identical all-layer-0 segment, the next cold start re-quarantined it, accumulating drift dirs (221 MB in the reported case) with no ingestion involved. Use the persist-completion marker as the discriminator instead. ChromaDB writes index_metadata.pickle last, so an intact pickle envelope proves the flush finished and the empty link_lists.bin is the legitimate all-layer-0 shape. Only treat an empty link_lists.bin as a partial flush when there is real payload AND no completion marker (absent or truncated pickle). The #1457 partial-flush protection (real payload, no/truncated marker) is preserved; the byte-sniff is factored into _hnsw_metadata_marker_intact and reused by _segment_appears_healthy. Also fixes the related single-writer stale-quarantine false positive (#1564), which shares this all-layer-0 root cause.
fix(chroma): stop quarantining valid all-layer-0 HNSW segments (#1716)
Concurrent killed-mid-write mines can leave embedding_fulltext_search in a malformed-inverted-index state that fails PRAGMA quick_check while the underlying rows stay intact (integrity_check ok). The repair preflight then hard-aborts before reaching the FTS5 rebuild step, so `mempalace repair` refuses to run and full-text search stays broken — the exact loop #1596 reports. The MineValidationError banner even promises "repair --yes rebuilds the FTS5 virtual table automatically," which the preflight abort made false. Add maybe_autoheal_fts5_index(): when every quick_check error is an isolated "malformed inverted index for FTS5 table" failure, rebuild the index in place from the intact embedding_fulltext_search_content table (INSERT ... VALUES('rebuild')) under mine_palace_lock, then re-run quick_check. The rebuild touches no drawer rows. Wired into both repair preflights (rebuild_index and cli cmd_repair). Any non-FTS5 error in the set, a lock held by a live mine, or a rebuild that does not clear quick_check leaves the errors unchanged so the caller still aborts with the recovery banner — broader corruption is never silently rebuilt over.
fix(repair): auto-heal isolated FTS5 inverted-index corruption (#1596)
* fix(mcp): stop clobbering host app root logger at import (#1860) _init_logging() ran at import and called logging.basicConfig(force=True), resetting the root logger's level, format, and handlers unconditionally. An app that configured logging before importing mempalace.mcp_server lost its setup: a host on DEBUG dropped to INFO, custom formatters and handlers were replaced. force=True existed (#1495) only to keep MEMPALACE_LOG_FILE working when root already had handlers. This keeps that contract without the reset: configure root only when it is unconfigured (standalone); otherwise attach a mempalace-filtered file handler additively and leave the host's config alone. Adds _MempalaceLogFilter so the file captures every mempalace logger (the dotted mempalace.* family plus the flat mempalace_* names) and nothing else. * fix(mcp): survive importlib.reload and pin file log format (#1860) Addresses review on #1885. Restore _logging_configured from globals() so the idempotency guard survives importlib.reload: a reload re-executes the module body, and a plain reset would let _init_logging() stack a duplicate file handler on root. Set an explicit "%(message)s" formatter on the file handler so the embedded path does not depend on logging's default formatter (which already renders the same, but is now pinned and identical to the standalone path). Adds a reload regression test and a format-pin assertion.
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@v6...v7) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3 to 4. - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](docker/setup-qemu-action@v3...v4) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3 to 4. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](docker/setup-buildx-action@v3...v4) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.18 to 0.15.20. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](astral-sh/ruff@0.15.18...0.15.20) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.20 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…detect() (#1893) (#1896) * fix(chroma): require SQLite magic header for ChromaBackend.detect() (#1893) Closes #1893. ChromaBackend.detect() was returning True for a 0-byte chroma.sqlite3 file because the check was just os.path.isfile(...). On a palace that has any other backend marker alongside a stale 0-byte chroma.sqlite3, resolve_backend_name then raises BackendMismatchError and the palace becomes unopenable until the user manually rm's the empty file. The 0-byte file appears as a side effect of any sqlite3.connect() on a missing path — Python creates the file immediately but writes the SQLite header only on the first statement. So any code path that touches the chroma.sqlite3 path with bare sqlite3.connect(), including chromadb's own PersistentClient lazy-init (see the comment at backends/chroma.py:2052), can leave a 0-byte artifact behind. Fix: detect() now reads the first 16 bytes and compares to the SQLite magic prefix b"SQLite format 3\x00" instead of relying on file presence alone. One extra open() + 16-byte read; detect() isn't a hot path. Properties: - Rejects 0-byte files (the symptom #1893 is about). - Rejects non-SQLite garbage at the canonical path (partial writes, etc.). - Doesn't false-negative on real chroma palaces: any chroma palace whose PersistentClient has done any work has the magic header on disk (verified — CREATE TABLE is enough to land the header). - Doesn't couple detect() to chroma's specific schema; the magic header is stable across chromadb releases. Test sweep: many test files used (chroma.sqlite3).touch() or .write_bytes(b"") as a "fake palace" shortcut, exploiting the loose isfile() check (one such site even had the comment "# pass the isfile guard"). After this change, those stand-ins no longer register as chroma palaces. Introduced tests/_chroma_palace_helper.py::make_minimal_chroma_sqlite following the existing _backend_conformance.py precedent, and updated 15 call sites across 8 test files to use it. The existing test_chroma_detect_matches_palace_with_chroma_sqlite (which encoded the buggy semantics with write_bytes(b"")) is renamed to test_chroma_detect_matches_palace_with_sqlite_header and now writes a real SQLite database via the helper. Added two new tests for the rejection paths (empty file, non-SQLite garbage). Full env-cleared suite: 3137 passed, 20 skipped, 0 failed. ruff check and ruff format --check both clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KC5Qsknh2zFRtRvVyjXiTA * fix(sqlite_exact): require SQLite magic header for SQLiteExactBackend.detect() Per gemini-code-assist review on #1892 PR #1896: SQLiteExactBackend has the same os.path.isfile() detection pattern as ChromaBackend did, with the same 0-byte-file vulnerability. Mirrors the chroma fix for repo-wide consistency. - SQLiteExactBackend.detect() now does the same 16-byte SQLite magic-prefix check as ChromaBackend.detect(). - _chroma_palace_helper.py: factored its body into a private _write_minimal_sqlite_file() and gained a sibling make_minimal_sqlite_exact_sqlite() for the sqlite_exact filename. No churn to any existing chroma call sites. - test_sqlite_exact_backend.py:426 (the one site that wrote b"" for sqlite_exact.sqlite3) updated to use the new helper. - Three new tests in test_sqlite_exact_backend.py mirror the chroma trio: matches with valid header, rejects empty file, rejects non-SQLite garbage. Full env-cleared suite: 3140 passed, 20 skipped, 0 failed. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KC5Qsknh2zFRtRvVyjXiTA --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…follow-up) (#1892) * fix(pgvector): skip document column for metadata-only fetches (#1840 follow-up) Closes the explicit "separate follow-up to keep this low-risk" callout in PR #1840's description. For remote pgvector deployments (TLS over WAN), `mempalace_status` and every other metadata-only consumer was transferring the full `document` column over the wire even when nothing read it. A single scroll over a 177K-drawer palace on a 175 ms-RTT link moved ~150 MB of document text plus ~50 MB of metadata; this PR drops that to ~50 MB. scroll_rows / _scroll gain `with_document: bool = True`. When False, SELECT projects NULL::text instead of the document column. Positional _row parser unchanged (record[1] stays the document slot, just receives NULL). Existing callers default to True and see byte-for-byte identical behavior. PgVectorCollection.get_all_metadata override: where=None path goes single-scroll with with_document=False. Filtered path falls back to base to keep _matches_where running on array/object metadata values (same correctness contract as #1840's filtered-path decision). Tests: - Update _FakePgVectorClient.scroll_rows to accept with_document; mirror the NULL-becomes-empty-string semantics when False - Update 5 existing scroll_calls assertions to include with_document=True (unchanged intent) - test_pgvector_get_all_metadata_skips_document_column: assert exactly one scroll call with with_document=False - test_pgvector_get_all_metadata_filtered_falls_back_to_base: assert filtered path preserves with_document=True Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KC5Qsknh2zFRtRvVyjXiTA * fix(pgvector): extend with_document=False fast path to filtered get_all_metadata Per gemini-code-assist review feedback on #1892: _matches_where only reads metadata, so the where=None vs where=set conditional fall-back was unnecessary. The filtered path can use the same single-scroll with_document=False fast path and apply the post-filter locally on metadata dicts — extending the wire-byte win to every get_all_metadata caller, not just unfiltered ones. Mirrors the pushdown + local _matches_where pattern already used by _rows in the same file: pushdown when _requires_local_filter is False, post-filter in Python otherwise. Same correctness contract as #1840's filtered get path. Renames test_pgvector_get_all_metadata_filtered_falls_back_to_base to test_pgvector_get_all_metadata_filtered_uses_fast_path and asserts the new behavior (with_document=False + pushdown forwards the equality filter to SQL). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KC5Qsknh2zFRtRvVyjXiTA --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* feat(convo): preserve authored timestamp from transcripts Conversation drawers only carried `filed_at` (ingest time), so a bulk re-mine collapsed every drawer to a single instant and the chronological signal was lost — even though each Claude Code / Codex JSONL line already carries an ISO-8601 `timestamp`. The recency-window fallback and any date-aware consumer then saw ingest order, not when content was written. - convo_miner: derive `authored_at` (per-file max line `timestamp`) and store it as drawer metadata; falls back to `filed_at` when absent - searcher: surface `authored_at` in search results, and break exact hybrid-score ties toward the more recently authored drawer (ISO strings sort chronologically; missing dates sort oldest) — benchmark-neutral as it only reorders exact ties - tests: cover `_extract_authored_at` (latest wins, skips/tolerates lines without timestamps, non-jsonl/missing -> None) and the tie-break Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(search): surface authored_at in CLI + backfill for existing data Completes the authored_at work so the field is visible end-to-end and existing palaces can adopt it without re-mining. - layers: CLI `search` output shows an `authored:` date line per result (peer of the existing date; markdown drawers fall back to filed_at) - scripts/backfill_authored_at.py: in-place migration that stamps authored_at on convos drawers from their source transcripts — metadata only (no re-embedding), idempotent, dry-run by default - docs/authored-at.md: documents created_at (ingest) vs authored_at (written) and both backfill paths (in-place / drop-and-recreate) - tests: backfill integration tests over an ephemeral ChromaDB collection Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(search): address review — non-string timestamp guard + top-level authored_at tiebreak Two correctness fixes from the PR review: - _extract_authored_at: only compare when the parsed `timestamp` is a str. A non-string timestamp on a malformed/foreign JSONL line previously raised TypeError outside the try and could crash the mine. - _hybrid_rank: the tie-break read `authored_at` only from nested `metadata`, but the search_memories path (MCP / Claude Code) carries it at the top level of each hit — so the tie-break silently no-op'd there. Read both shapes. - tests: non-string timestamp cases, and a top-level-shape tie-break test (which fails before this fix). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * style: apply ruff format to authored_at changes CI ruff format --check flagged 4 files; ruff check already passed. Formatting only — no behavior change. --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Igor Lins e Silva <4753812+igorls@users.noreply.github.com>
…ntention fix(repair): wait out transient SQLite contention
fix(searcher): guard CLI divergence before Chroma open
…anup fix(repair): require clean SQLite recovery finalization
…ph-scope fix: scope derived graph state to explicit palace
chore(release): 3.6.0
There was a problem hiding this comment.
Pull request overview
Promotes develop to main for the v3.6.0 release, bringing the codebase, tests, docs, and plugin/website surfaces into alignment with the new release features (remote/team server, new backends, KG supersession, authored timestamps, etc.).
Changes:
- Bump release/version surfaces to 3.6.0 and refresh changelog + docs/tooling counts (36 MCP tools).
- Add/expand backend + server functionality (Milvus backend registration/config/docs; remote/team server docs + deploy templates; facets + metadata-only paths).
- Strengthen reliability and correctness via new behaviors + substantial new test coverage (WAL hardening, authored_at handling/backfill, HNSW divergence guard, lock re-entrancy, etc.).
Reviewed changes
Copilot reviewed 104 out of 105 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| website/reference/modules.md | Update tool-count docs |
| website/reference/mcp-tools.md | Document 36 tools + kg_supersede |
| website/reference/cli.md | Document --backend flag |
| website/guide/remote-server.md | New remote/team server guide |
| website/guide/openclaw.md | Update tool-count docs |
| website/guide/mcp-integration.md | Update tool-count docs |
| website/guide/configuration.md | Backend configuration reference expansion |
| website/guide/claude-code.md | Update tool-count docs |
| website/.vitepress/theme/style.css | Table layout/overflow improvements |
| website/.vitepress/config.mts | Add remote-server nav item |
| tests/test_wal.py | WAL behavior tests |
| tests/test_sync.py | Use minimal chroma sqlite helper |
| tests/test_sqlite_exact_backend.py | sqlite_exact detection + helpers |
| tests/test_serve.py | New mempalace serve tests |
| tests/test_searcher.py | CLI HNSW divergence routing tests |
| tests/test_qdrant_backend.py | Qdrant facet_counts tests |
| tests/test_pgvector_backend.py | pgvector metadata-only fast path tests |
| tests/test_palace.py | Minimal chroma sqlite helper usage |
| tests/test_palace_locks.py | Cross-thread re-entrant lock tests |
| tests/test_palace_graph_tunnels.py | Config-scoped tunnel persistence tests |
| tests/test_normalize.py | TOCTOU-safe normalize size test update |
| tests/test_miner.py | exclude_patterns + tunnel config threading tests |
| tests/test_mcp_http_transport.py | Read-only + TLS + disconnect handling tests |
| tests/test_knowledge_graph.py | Supersession boundary regression tests |
| tests/test_hybrid_search.py | Tie-break ordering tests |
| tests/test_hooks_cli.py | _safe_wing_slug + property-based tests |
| tests/test_hnsw_payload_health.py | All-layer-0 HNSW validity tests |
| tests/test_hallways.py | Config-scoped hallway persistence tests |
| tests/test_format_miner.py | Config threading in format miner tests |
| tests/test_entity_detector.py | LaTeX prose detection tests |
| tests/test_entities.py | New structural-entity extractor tests |
| tests/test_embedding_wrapper.py | Wrapper forwarding regression tests |
| tests/test_daemon.py | Detached spawn kwargs tests |
| tests/test_convo_miner.py | mtime-aware convo re-mine tests |
| tests/test_convo_miner_unit.py | Scan filters + authored_at extraction tests |
| tests/test_config.py | Milvus config + palace_path override tests |
| tests/test_config_palace_path.py | ~ expansion tests |
| tests/test_cli.py | Repair rebuild-index dispatch/exit tests |
| tests/test_cli_hallways.py | New hallways CLI tests |
| tests/test_clean_nul_bytes.py | NUL-byte sanitization tests |
| tests/test_backfill_authored_at.py | Backfill script integration tests |
| tests/test_backends.py | Robust chroma detect tests |
| tests/conftest.py | Ensure chromadb clients are closed in tests |
| tests/_chroma_palace_helper.py | New minimal sqlite marker helper |
| skills/mempalace/SKILL.md | Update tool-count docs |
| scripts/backfill_authored_at.py | New authored_at backfill script |
| README.md | Backend table + version/tool-count updates |
| pyproject.toml | Version bump + Milvus entry point/extra + Ruff pin |
| mempalace/version.py | Version bump |
| mempalace/service.py | Tool classification update |
| mempalace/searcher.py | Tie-break + HNSW divergence preflight + authored_at surfacing |
| mempalace/palace_graph.py | Config threading for tunnel persistence |
| mempalace/normalize.py | TOCTOU-safe open + size/type guards |
| mempalace/migrate.py | Preserve symlinks in backups |
| mempalace/llm_client.py | Avoid env-key use on external probes |
| mempalace/layers.py | Surface authored date in output |
| mempalace/knowledge_graph.py | Half-open temporal filter + supersede primitive |
| mempalace/hooks_cli.py | Windows console suppression + safe wing slugging |
| mempalace/hallways.py | Config-scoped persistence + CLI plumbing |
| mempalace/format_miner.py | Use selected palace config for tunnels |
| mempalace/entity_detector.py | Treat LaTeX as prose |
| mempalace/entities.py | New structural entity extraction |
| mempalace/daemon.py | Windows detached flags revision + docs |
| mempalace/config.py | NUL stripping + Milvus config + palace_path override |
| mempalace/backends/sqlite_exact.py | SQLite magic-header detect |
| mempalace/backends/registry.py | Register Milvus backend |
| mempalace/backends/qdrant.py | Add facet_counts + capability |
| mempalace/backends/pgvector.py | Skip document column for metadata-only reads |
| mempalace/backends/embedding_wrapper.py | Forward concrete BaseCollection methods |
| mempalace/backends/chroma.py | HNSW completion marker + NUL stripping + detect hardening |
| mempalace/backends/base.py | Add facet_counts API |
| mempalace/backends/init.py | Export Milvus symbols |
| integrations/openclaw/SKILL.md | Update version + tool surface docs |
| hooks/mempal_save_hook.sh | Use python -m mempalace invocation |
| hooks/mempal_precompact_hook.sh | Use python -m mempalace invocation |
| docs/authored-at.md | New authored_at documentation |
| deploy/server.env.example | New remote server env template |
| deploy/mempalace-server.service | New systemd unit template |
| deploy/docker-compose.server.yml | New compose deployment template |
| CHANGELOG.md | Add 3.6.0 release notes |
| .github/workflows/version-guard.yml | Update checkout action version |
| .github/workflows/publish.yml | Update checkout action version |
| .github/workflows/docker-publish.yml | Update checkout + docker action versions |
| .github/workflows/deploy-docs.yml | Update checkout action version |
| .github/workflows/ci.yml | Update checkout action version |
| .cursor-plugin/README.md | Update tool-count docs |
| .cursor-plugin/plugin.json | Update tool-count docs |
| .cursor-plugin/marketplace.json | Update tool-count docs |
| .codex-plugin/README.md | Update tool-count docs |
| .codex-plugin/plugin.json | Version + tool-count updates |
| .claude-plugin/README.md | Update tool-count docs |
| .claude-plugin/plugin.json | Version + tool-count updates |
| .claude-plugin/marketplace.json | Version + tool-count updates |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| fd = os.open(filepath, flags) | ||
| file_stat = os.fstat(fd) | ||
| if not stat.S_ISREG(file_stat.st_mode): | ||
| raise IOError(f"Could not read {filepath}: not a regular file") | ||
| if file_stat.st_size > 500 * 1024 * 1024: # 500 MB safety limit |
chore: sync main hotfixes into develop before 3.6.0
mempalace_checkpoint hard-coded added_by="checkpoint" for every drawer, dropping the filing agent's identity even though it arrives in the same call via diary.agent_name. Add an optional top-level added_by parameter and resolve attribution as explicit > diary agent_name > "checkpoint"; blank/whitespace/non-string values defer to the next source. The value is declared in the tool schema so tools/call admits it on both stdio and HTTP transports. Fixes #2023 Co-Authored-By: epinethrone <172391900+epinethrone@users.noreply.github.com>
The new real-Chroma checkpoint test dropped its create-time client with a bare del, leaving the per-path SharedSystemClient's SQLite/HNSW handles open on Windows (#1128). Close both clients so the temp palace is released, matching the conftest fixture's close-not-del pattern.
fix(mcp): preserve agent attribution in mempalace_checkpoint (#2023)
feat(routing): add shared daemon write-routing policy
There was a problem hiding this comment.
SUCH GREAT WORK IGOR! I've been thinking about some of the improvements and though you always do such meticulous and clean work these new additions are really important for the MemPalace philosophy and creating a richer, safer and more nuanced experience for people!🥳 -m
Promote
develop→mainfor the 3.6.0 releaseReleases publish only from
main(per docs/RELEASING.md). This promotes the 3.6.0 bump merged via #2019 plus the work accumulated ondevelopsince v3.5.0 — 50 PRs.Version
All release/version surfaces are 3.6.0, the lockfile is regenerated, and the documented/runtime MCP surface is aligned at 36 tools. PR #2019 passed the version guard, lint, package builds, and the full Linux 3.9/3.11/3.13, macOS, and Windows matrix.
Headline changes since v3.5.0
Features
mempalace servewith bearer auth, TLS, read-only enforcement, Docker Compose, and systemd deploymentmempalace_kg_supersedefact replacement with half-open temporal boundariesauthored_at, date-window drawer listing, mined-session graph derivation, project exclusions, and LaTeX source coveragePerformance
Reliability
--palacegraph state stays isolatedFull detail is in CHANGELOG.md under
## [3.6.0].After merge
Draft a GitHub Release targeting
main, tagv3.6.0. Publishing the release triggerspublish.yml; approve the gatedpypienvironment upload.