Skip to content

V0.2.17

Latest

Choose a tag to compare

@het0814 het0814 released this 24 Aug 18:20
· 7 commits to main since this release
51b57a2

Release Notes for v0.2.17

This release hardens the data-integrity path across recall, migration, and OKF round-trips. Recall no longer silently drops imported memories or mangles filter-only queries; update_memory stops clobbering schema-external metadata; OKF exports are now staged and swapped atomically under a cross-process lock so a concurrent import can never observe a half-written bundle. Daily analysis stops overflowing the embedding and LLM context windows on busy days, memanto memory sync always writes a fresh export, agent deletion revokes tokens before removing metadata, and the TypeScript SDK's file upload is rebuilt on standard FormData.

Improvements

  • Atomic OKF bundle replacement with cross-process locking (memanto/app/services/okf_export_service.py, memanto/app/utils/atomic_write.py, memanto/cli/migrate/okf_loader.py)
    • Exports now render into a staging directory and swap into place, instead of overlaying new files onto the existing bundle — deleted or renamed memories can no longer be resurrected on the next import.
    • A failed render leaves the last good bundle intact; a failed final rename restores the previous snapshot from backup.
    • New okf_bundle_lock() helper provides a path-scoped, cross-process reader/writer lock (fcntl on POSIX, msvcrt on Windows with non-blocking retry so contention never hits LK_LOCK's finite retry limit). The loader holds the shared lock across discovery and every file read, so an exporter cannot move the bundle aside mid-load.
    • Lock files are intentionally never unlinked — removing one lets a waiter hold a lock on a stale inode while a new caller locks the replacement.
  • OKF round-trip preserves more metadata (memanto/app/services/okf_export_service.py, memanto/cli/migrate/mappers.py)
    • x_memanto frontmatter now carries updated_at, expires_at, and ttl_seconds alongside the existing id/confidence/provenance/source/status.
    • Import validates provenance against VALID_PROVENANCE_TYPES and falls back to imported rather than trusting arbitrary strings, and preserves the source updated_at instead of stamping migration time.
  • Bounded session digest for daily summaries and conflict scans (memanto/app/services/daily_analysis_service.py)
    • _truncate_embedding_query() now builds a 10-chunk digest that samples evenly across the whole session text instead of hard-truncating to the first N tokens, so late-day activity is still represented.
    • The digest is used in the prompt as well as the embedded query, keeping long days inside both the embedding and LLM context windows.
  • memanto memory sync always runs a fresh export (memanto/cli/client/direct_client.py, memanto/cli/client/sdk_client.py, memanto/cli/commands/memory_mgmt.py)
    • The old cache fast-path meant memories written earlier in the same session were missing from the project's MEMORY.md. Sync now exports first and only falls back to the previous export when the backend is unreachable, reported as source: "stale-cache".
    • The --limit help text and the cache source label were updated to match.
  • Expanded preference-negation lexicon (memanto/app/services/memory_parsing_service.py)
    • Added can not stand alongside can't stand/cannot stand, and detest/loathe/despise to the dislike group.
    • Raised the dislike group's weight from 3 to 5 so a negative preference outranks a relationship match on the same sentence.
  • Shared conflicts directory (memanto/app/config.py, memanto/app/services/daily_analysis_service.py, memanto/app/ui/routes/ui_router.py, memanto/cli/client/*.py, memanto/cli/commands/memory.py)
    • New get_conflicts_dir() / get_conflict_report_path() replace five hardcoded Path.home() / ".memanto" / "conflicts" constructions, so writers and readers agree on one location under the active data directory.
  • MemoryError renamed to MemoryOperationError (memanto/app/utils/errors.py)
    • The internal exception no longer shadows Python's builtin MemoryError. A MemoryError = MemoryOperationError alias is retained for external integrations such as MCP. The HTTP error payload's error field now reads MemoryOperationError.
  • Pydantic V2 validator migration (memanto/app/utils/validation.py)
    • All 5 @validator decorators replaced with @field_validator + @classmethod, clearing the V1 deprecation warnings. No behavioral change.
  • Web UI typography and loading indicator (memanto/app/ui/static/index.html, memanto/app/ui/static/ant.svg, memanto/app/ui/static/logo.svg)
    • Switched the body font from Inter to JetBrains Mono via a new --font-sans token, and replaced the CSS border spinner with an ant glyph asset (48px in the full-page overlay, 22px inline).

Bug Fixes

  • parse_relative_time() silently returned None for natural-language windows (memanto/app/utils/temporal_helpers.py)
    • "last week", "last month", and "last year" all fell through to the no-filter sentinel, so callers returned all memories instead of recent ones — the timeline-amnesia bug class.
    • Added last/past week|month|year (7/30/365 days), past ... as a synonym for last ..., word-number parsing (zerotwenty, plus thirty/forty/fifty), and whitespace collapsing so "last 7 days" parses. Lookup tables moved to module level.
    • get_last_n_days/get_last_n_hours are now guarded against OverflowError on pathological inputs like "last 9999999999 days", returning None instead of crashing.
  • Recall silently dropped memories with unknown confidence (memanto/app/services/memory_read_service.py)
    • Memories with None/missing confidence were filtered out whenever min_confidence > 0, which primarily hit memories imported via memanto migrate. Unknown confidence now fails open, matching how expiration filtering handles unparseable dates. OverflowError joins TypeError/ValueError in the parse guard.
  • Filter-only queries carried a leading space (memanto/app/services/memory_read_service.py)
    • An empty query plus filters produced " #memory_type:fact", which can confuse Moorcheh query parsing. _build_filtered_query() now strips and joins correctly for both the empty and non-empty cases.
  • update_memory() overwrote schema-external metadata (memanto/app/services/memory_read_service.py, memanto/app/services/memory_write_service.py, memanto/app/constants.py)
    • _format_memory_item() stripped unknown metadata keys (e.g. original_id from on-prem data_store.json) on read, so the preservation logic in update_memory() never received them. Extra keys are now passed through, excluding known schema keys, memory_type (a duplicate of type), and the new shared REMOVED_TRUST_FIELDS frozenset.
    • Carry-forward on update now keys off an explicit _MEMORY_SCHEMA_FIELDS set rather than "not already in the document", so an omitted optional field (tags=[], source_ref=None) is correctly treated as an intentional clear instead of being restored from the old record.
  • Batch write reported the wrong submitted count (memanto/app/services/memory_write_service.py)
    • total_submitted used len(memories) rather than len(results), over-reporting when items were rejected before submission.
  • Session summaries logged session_id: "unknown" (memanto/cli/client/direct_client.py, memanto/cli/client/sdk_client.py)
    • Both clients already resolved a validated session but discarded it and hardcoded "unknown" when writing the local Markdown summary. They now log the real session.session_id for both remember and batch_remember.
  • Malformed batch responses were partially tolerated (memanto/cli/client/direct_client.py, memanto/cli/client/sdk_client.py)
    • A missing results key, or a results array whose length doesn't match the submitted records, now raises MemoryOperationError instead of silently pairing memories with None results.
  • Namespace limit errors surfaced as generic failures (memanto/app/services/agent_service.py)
    • Moorcheh tier/quota/limit rejections during agent creation now raise a typed NamespaceError with the real cause; conflict detection was restructured to catch ConflictError alongside message-based matching, and all failures chain the original exception.
  • Renewed session tokens were unreadable by browser clients (memanto/app/main.py)
    • X-Session-Token added to the CORS expose_headers list — custom response headers are not CORS-safelisted, so header-authenticated clients could not read an auto-renewed token.
  • memanto session info compared timezones incorrectly (memanto/cli/commands/session.py)
    • Replaced the deprecated datetime.utcnow() and inverted tzinfo handling: naive expiry timestamps are now assumed UTC and compared against datetime.now(timezone.utc), instead of stripping tzinfo from aware ones.
  • Negative session durations were accepted (memanto/app/services/session_service.py)
    • create_session() now rejects non-numeric or negative duration_hours with a ValueError instead of minting an already-expired session.
  • LangGraph store crashed on unexpected list_agents payloads (integrations/langgraph/langgraph_memanto/store.py, nodes.py)
    • The store iterated the response directly; a dict payload ({"agents": [...]}) or non-dict entries raised instead of degrading. The shape is now validated and logged, returning [] on anything unrecognized.
    • The remember node caps joined message content at 10,000 chars (MemoryRecord.content max length), keeping the tail — long conversations were previously rejected and dropped silently.
  • Mem0 category strings split into character tags (memanto/cli/migrate/mappers.py)
    • A single category string was iterated per-character. _normalize_mem0_categories() now treats a bare string as one category and handles lists, tuples, and sets uniformly.
  • Supermemory migration lost data (memanto/cli/analyze/supermemory_export.py, memanto/cli/migrate/mappers.py, memanto/cli/migrate/runner.py)
    • The v4 list endpoint takes containerTag (singular), not containerTags — the wrong parameter silently under-fetched.
    • Cross-tag deduplication discarded a memory's additional container tags; rows now accumulate a merged container_tags list while keeping the singular container_tag per bucket for compatibility.
    • The document-chunk fallback only ran when memories[] was entirely empty, so unprocessed documents in mixed accounts were dropped. Chunks are now harvested for any document not represented by a mapped memory, and source_count() mirrors that logic so the pre-migration summary matches what is actually imported.
  • OKF migration rejected long titles and boolean temporal metadata (memanto/cli/migrate/mappers.py)
    • Titles over MemoryRecord.title's 100-char limit invalidated the whole batch; they are now truncated with the original preserved in the [Supporting data] footer.
    • _parse_dt() guards against bool (which subclasses int), where YAML true previously became 1970-01-01T00:00:01Z and could expire a durable memory. _parse_positive_int() rejects fractional floats rather than truncating them.
  • Invalid migration export files raised raw tracebacks (memanto/cli/commands/migrate.py, memanto/app/ui/routes/ui_router.py)
    • Unreadable or non-JSON export files now produce a clear ValueError in the CLI and an HTTP 400 in the UI, instead of an unhandled JSONDecodeError/OSError.
  • TypeScript SDK file upload rebuilt on FormData (sdks/typescript/src/index.ts)
    • Replaced the hand-rolled multipart stream (createReadStream + manual boundary/Content-Length + duplex: "half") with openAsBlob() and standard FormData, removing the escapeMultipartValue filename-escaping workaround.

Security

  • Agent deletion now revokes the session token first (memanto/app/routes/sessions.py)
    • delete_agent() removed agent metadata before revoking the persisted session, so a failure in local session cleanup left an apparently deleted agent whose old token still authorized requests. The order is now reversed, aborting the deletion if revocation fails.
  • Conflict report paths validated against traversal (memanto/app/config.py)
    • get_conflict_report_path() validates agent_id against ^[\w\-]+$ and date against ^\d{4}-\d{2}-\d{2}$ before joining, replacing the unvalidated f-string path construction used by the CLI clients and UI router.

Tests

  • tests/test_okf.py — bundle re-export replaces stale entries, a failed re-export preserves the last good bundle, the loader waits for bundle replacement, single-file loads take the bundle lock, invalid temporal extensions are ignored, and invalid provenance falls back to imported.
  • tests/test_unit.py — file-lock coverage consolidated here, including Windows contention retry without a deadline and non-retry on unexpected errors; original_id survives the full read-format-update cycle; batch clients preserve temporal metadata.
  • tests/test_migrate.py — Mem0 single-category-string handling and the Supermemory container-tag/unprocessed-document fixes (moved and consolidated from tests/test_migrate_runner.py).
  • tests/test_temporal_helpers.py — natural-language and word-number relative-time inputs, numeric last-N-days/hours, and both overflow cases, asserting returned timestamps land within ±1 day/hour of expected.
  • tests/test_daily_analysis_query_length.py (renamed from the prior query-length file) — a busy-day conflict report keeps the embedded query inside the context window.
  • tests/test_export_resilience.py — fresh export replaces a stale cache, and the cache is used when the backend is down.
  • tests/test_cli.pyload_export rejects non-object JSON; direct sync exports fresh before copying.
  • tests/test_memory_parsing.py — expanded negation lexicon and negative-preference ranking.

Full Changelog

Full Changelog: v0.2.16...v0.2.17