Skip to content

release: merge dev-v2.0.23 into main#2085

Merged
CarltonXiang merged 46 commits into
mainfrom
dev-v2.0.23
Jul 9, 2026
Merged

release: merge dev-v2.0.23 into main#2085
CarltonXiang merged 46 commits into
mainfrom
dev-v2.0.23

Conversation

@CarltonXiang

Copy link
Copy Markdown
Collaborator

Description

Please include a summary of the change, the problem it solves, the implementation approach, and relevant context. List any dependencies required for this change.

Related Issue (Required): Fixes #issue_number

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactor (does not change functionality, e.g. code style improvements, linting)
  • Documentation update

How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration

  • Unit Test
  • Test Script Or Test Steps (please provide)
  • Pipeline Automated API Test (please provide)

Checklist

  • I have performed a self-review of my own code | 我已自行检查了自己的代码
  • I have commented my code in hard-to-understand areas | 我已在难以理解的地方对代码进行了注释
  • I have added tests that prove my fix is effective or that my feature works | 我已添加测试以证明我的修复有效或功能正常
  • I have created related documentation issue/PR in MemOS-Docs (if applicable) | 我已在 MemOS-Docs 中创建了相关的文档 issue/PR(如果适用)
  • I have linked the issue to this PR (if applicable) | 我已将 issue 链接到此 PR(如果适用)
  • I have mentioned the person who will review this PR | 我已提及将审查此 PR 的人

Reviewer Checklist

  • closes #xxxx (Replace xxxx with the GitHub issue number)
  • Made sure Checks passed
  • Tests have been provided

liuhaibin0528 and others added 30 commits May 8, 2026 23:56
…ble probe

When the user provides an endpoint that already includes the full path
(e.g. https://open.bigmodel.cn/api/paas/v4/chat/completions for
Zhipu AI / GLM models), the probe would previously append another
/v1/chat/completions segment, resulting in a malformed URL.

This fix checks if the endpoint already ends with the target path
(/chat/completions or /embeddings) before appending, making the
probe compatible with endpoints that specify the full URL.

Affects both probeChat and probeEmbedding for openai_compatible provider.
docs(memos-local-plugin): clarify install path and stale dir names (#1540)

The README's 'Quick start' section told users to use install.sh instead
of npm install, but the warning was buried and users still tried
'npm install -g @memtensor/memos-local-plugin' first. The reporter in
#1540 encountered this on a Hermes deployment.

This change:

- Promotes the 'do not run npm install -g' notice to a prominent
  IMPORTANT callout explaining why global install is wrong (no
  agent-home deploy, no config.yaml, no bridge/viewer) and that the
  tarball intentionally ships built artifacts only.
- Adds a Troubleshooting subsection covering the two specific symptoms
  in the bug report: the 'package not found' misread, and the stale
  web/ and site/ directory names (web/ is now viewer/, site/ was
  removed by commit 26e7e3d).
- Mentions install.ps1 for Windows alongside install.sh.
- CHANGELOG: record the docs fix and reference #1540.

Documentation-only change; no code or runtime behavior touched.

Co-authored-by: MemOS AutoDev <autodev@memtensor.ai>
Co-authored-by: Matthew <heimixiaozhuang@zju.edu.cn>
…n unexpected keyword a (#1889)

fix: remove invalid chunker parameter from SystemParser test instantiation

- SystemParser.__init__() signature changed to (embedder, llm=None)
- Test was still passing chunker=None causing TypeError
- Fixes all 5 failing tests in test_system_parser.py

Fixes #1888

Co-authored-by: MemOS AutoDev <autodev@memos.ai>
Co-authored-by: Matthew <heimixiaozhuang@zju.edu.cn>
…ror when given None (#1884)

* test: add comprehensive tests for clean_json_response (issue #1525)

- Add test suite in tests/mem_os/test_format_utils.py
- Cover None input ValueError with diagnostic message
- Cover markdown removal, whitespace stripping, edge cases
- Verify fix for AttributeError when LLM returns None

* style: format clean_json_response tests

---------

Co-authored-by: MemOS AutoDev <autodev@memos.ai>
Co-authored-by: Matthew <heimixiaozhuang@zju.edu.cn>
…_access — fails for ev (#1903)

fix: validate current user not target in share_cube_with_user (#1901)

share_cube_with_user(cube_id, target_user_id) called
_validate_cube_access(cube_id, target_user_id), but the validator
signature is (user_id, cube_id). The cube_id therefore landed in the
user_id slot and _validate_user_exists raised
"User '<cube_id>' does not exist or is inactive" for every well-formed
call, making the API unusable.

The in-code comment "Validate current user has access to this cube"
already documented the correct intent: the sharing user (self.user_id)
must have access to the cube being shared, not the target. Switch the
call to self._validate_cube_access(self.user_id, cube_id). The target
user's existence is independently checked on the next line via
validate_user(target_user_id), so that path is unchanged.

Add regression tests in tests/mem_os/test_memos_core.py that pin down:
- validate_user_cube_access is consulted with (self.user_id, cube_id),
- add_user_to_cube is called with (target_user_id, cube_id) on success,
- a missing target raises "Target user '<id>' does not exist".

Closes #1901

Co-authored-by: MemOS AutoDev Bot <autodev@memtensor.local>
Co-authored-by: Matthew <heimixiaozhuang@zju.edu.cn>
runReflect's orphan-steps fallback was inserting new trace rows whenever
a recovered episode's snapshot timestamps didn't match existing DB rows.
This caused trace_ids_json to grow on every bridge restart, keeping
reward.traceCount != traceIds.length and looping forever — generating
48k+ empty traces and continuous OpenRouter traffic.

Guard: when meta.recoveryReason === "dirty_reward_rescore", log and skip
the insert instead of writing new rows. Legitimate orphan handling (test
paths, genuinely dropped events) is unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
daemon_manager.py: extend ensure_viewer_daemon health-poll deadline
from 15s to 45s to cover cold Node.js starts (tsx compile + SQLite
open + FTS warmup). After the first 15s, back off probes from 0.5s
to 2s to avoid hammering a slow-starting daemon.

__init__.py: increase _open_session timeout in init from 30s to 60s
so session.open doesn't time out before the daemon finishes starting.

Add a 1s double-spawn guard: when probe_viewer_status() returns
"free", wait 1s and re-probe before falling through to
ensure_viewer_daemon(). Eliminates the race where a second gateway
session spawns a second stdio bridge because the daemon from the
first session hasn't bound the port yet.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The RPC endpoint was unconditionally exempt from session-cookie auth
to allow the local Python adapter to connect without a browser session.
On hub-mode deployments (bindHost=0.0.0.0) this left the endpoint
reachable from the network without any auth when password protection
is enabled.

Now the exemption only applies when remoteAddress is 127.0.0.1, ::1,
or ::ffff:127.0.0.1. Standard installs (bindHost=127.0.0.1) are
unaffected; the Python adapter always connects from loopback and
continues to work. Network callers on hub deployments must hold a
valid session cookie.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- auth.ts: add reverse-proxy assumption comment to loopback RPC exemption
- capture.ts + memory-core.ts: extract magic string to RECOVERY_REASONS.DIRTY_REWARD_RESCORE in new recovery-constants.ts
- memory-core.test.ts: update assertions to use shared constant
- __init__.py: extract _connect_http_bridge() helper; fix unbound http_bridge on constructor exception; conditional cold-start sleep
- daemon_manager.py: guard negative sleep in backoff loop; add probe_viewer_status() + startup_lock_active() helpers
…ategyStructMemReader (#1794)

Co-authored-by: Memtensor-AI <project@memtensor.cn>
* docs: clarify API product model descriptions

* docs: normalize product model descriptions

---------

Co-authored-by: Lucas-FManager <luong.nguyen188@gmail.com>
Co-authored-by: Lucas-FManager <265058144+Lucas-FManager@users.noreply.github.com>
Co-authored-by: Memtensor-AI <project@memtensor.cn>
Fixes two occurrences of "monitering" → "monitoring" in
APIADDRequest and APIFeedbackRequest task_id field descriptions
in src/memos/api/product_models.py.

Co-authored-by: Memtensor-AI <project@memtensor.cn>
* docs(cn): translate remaining English text in dream.md

* chore: update github workflow stale.yaml

* fix: quote OpenClaw FTS query terms

---------

Co-authored-by: harvey_xiang <harvey_xiang22@163.com>
Co-authored-by: sonimwang <17816198144@163.com>
Co-authored-by: HarveyXiang <harvey_xiang@163.com>
…mOSClient signatures (#1919)

* docs(cn): translate remaining English text in dream.md

* docs: align Quick Start examples with MemOSClient signatures and add EN translations

Fix the Chat and Message API reference docs where Quick Start code
examples used parameters not present in the actual MemOSClient:

- chat/chat.md: replace readable_cube_ids/writable_cube_ids/mode with
  conversation_id/knowledgebase_ids to match client.chat() signature
- message/feedback.md: replace history/writable_cube_ids/retrieved_memory_ids/
  corrected_answer with conversation_id to match client.add_feedback()
- message/get_suggestion_queries.md: replace MemOSClient.get_suggestions()
  (method does not exist) with raw HTTP POST request; remove duplicate H1 heading

Also add English translations for all four files under docs/en/.

* chore: update github workflow stale.yaml

---------

Co-authored-by: harvey_xiang <harvey_xiang22@163.com>
Co-authored-by: sonimwang <17816198144@163.com>
Co-authored-by: jiachengzhen <jiacz@memtensor.cn>
Co-authored-by: HarveyXiang <harvey_xiang@163.com>
Co-authored-by: Memtensor-AI <project@memtensor.cn>
* docs(cn): translate remaining English text in dream.md

* chore: update github workflow stale.yaml

* docs: translate API reference pages

---------

Co-authored-by: harvey_xiang <harvey_xiang22@163.com>
Co-authored-by: sonimwang <17816198144@163.com>
Co-authored-by: HarveyXiang <harvey_xiang@163.com>
* docs(cn): translate remaining English text in dream.md

* chore: update github workflow stale.yaml

* fix: align Gemini viewer embedding test

* fix(openclaw): don't leak Gemini API key to user-supplied endpoints

OCR review on #2050 surfaced a security regression: the viewer Gemini
embedding probe appended ?key=<apiKey> to every URL, including URLs the
caller built themselves. This silently sent the user's Gemini API key to
a third-party server whenever a custom 'endpoint' was configured.

- When 'endpoint' is supplied, build the URL without any key parameter.
- When 'endpoint' is supplied, omit the hardcoded 'models/<default>'
  field so a proxy can route on its own model identifier.

Adds a regression test that asserts both branches.

---------

Co-authored-by: harvey_xiang <harvey_xiang22@163.com>
Co-authored-by: sonimwang <17816198144@163.com>
Co-authored-by: HarveyXiang <harvey_xiang@163.com>
Co-authored-by: Ziyang Guo <121015044+RunMarshal@users.noreply.github.com>
* docs(cn): translate remaining English text in dream.md

* chore: update github workflow stale.yaml

* fix: add durable Hermes provider link

* fix(plugin): harden Hermes provider-link installers (9 OCR findings)

OCR review on #2051 surfaced 9 robustness issues across the PowerShell
and Unix Hermes installers. This patch addresses every one of them:

PowerShell install.ps1:
  - Surface junction-creation failures via try/catch + -ErrorAction Stop
    instead of silently piping to Out-Null.
  - Log a warning if the plugin.yaml copy may have failed.
  - Log a message when the user-level plugin dir is auto-created.

Unix adapter install.hermes.sh:
  - Validate HOME is set before using it (avoid writing to filesystem
    root if HOME is empty).
  - Remove a pre-existing non-symlink entry at the user-level target
    before ln -sfn, matching install.sh semantics.
  - Clean up the legacy 'memos_provider' symlink name so re-running the
    installer doesn't leave a dangling reference.

Unix install.sh:
  - Move the cp of plugin.yaml BEFORE the symlink loop so the second
    symlink (user-level) already points at a fully populated provider
    directory.
  - Switch the rm + ln -s pattern to atomic ln -sfn (matching the
    adapter script) so a failed rm no longer aborts the install with
    'File exists'.

Adds four regression tests that lock the new behaviors in place.

---------

Co-authored-by: harvey_xiang <harvey_xiang22@163.com>
Co-authored-by: sonimwang <17816198144@163.com>
Co-authored-by: HarveyXiang <harvey_xiang@163.com>
Co-authored-by: Ziyang Guo <121015044+RunMarshal@users.noreply.github.com>
* docs(cn): translate remaining English text in dream.md

* chore: update github workflow stale.yaml

* fix: delimit memory context with XML boundary

---------

Co-authored-by: harvey_xiang <harvey_xiang22@163.com>
Co-authored-by: sonimwang <17816198144@163.com>
Co-authored-by: HarveyXiang <harvey_xiang@163.com>
* docs(cn): translate remaining English text in dream.md

* chore: update github workflow stale.yaml

* fix: load bridge package version without require

---------

Co-authored-by: harvey_xiang <harvey_xiang22@163.com>
Co-authored-by: sonimwang <17816198144@163.com>
Co-authored-by: HarveyXiang <harvey_xiang@163.com>
* docs(cn): translate remaining English text in dream.md

* chore: update github workflow stale.yaml

* fix: log llm error response bodies

---------

Co-authored-by: harvey_xiang <harvey_xiang22@163.com>
Co-authored-by: sonimwang <17816198144@163.com>
Co-authored-by: HarveyXiang <harvey_xiang@163.com>
…ponse (#2064)

* fix(skill-crystallize): retry with context when LLM returns empty response

* chore: add retry_succeeded log so successful rescues are visible
When the correction retry rescues a previously-failed crystallization,
log a warning with policyId and the original error so operators can
distinguish first-try successes from retry-rescued ones.

* fix: allow retry when raw response is empty string (not just null)
The condition  treats empty string as falsy, so when
the LLM returns blank output the correction retry was never triggered.
Changed to  so empty responses also get a retry.

---------

Co-authored-by: 34262315716 <34262315716@users.noreply.github.com>
* docs(cn): translate remaining English text in dream.md

* chore: update github workflow stale.yaml

* fix: avoid blocking OpenClaw registration rebuild

* fix(openclaw): alias pluginDir to moduleDir after detectPluginDir removal

OCR review on #2044 flagged that after removing detectPluginDir(moduleDir),
the local 'pluginDir' variable is still referenced on lines 270 and 273
(path.join(pluginDir, 'package.json') and new Telemetry(..., pluginDir)),
which would cause a ReferenceError at runtime.

Alias pluginDir = moduleDir at the same scope so the existing
references keep working without re-introducing the removed helper.

---------

Co-authored-by: harvey_xiang <harvey_xiang22@163.com>
Co-authored-by: sonimwang <17816198144@163.com>
Co-authored-by: HarveyXiang <harvey_xiang@163.com>
Co-authored-by: Ziyang Guo <121015044+RunMarshal@users.noreply.github.com>
…th (#1817)

## Summary

Three targeted fixes from companion infrastructure stabilization:

### 1. Skip orphan trace insert during dirty-reward recovery
(`fc7252fc`)
When `dirtyFlag` triggers a rescore on episodes with no matching turns,
the capture path inserts a trace with `r_reward=0` but no valid
`idParent`. This creates orphan rows that inflate the database and break
FTS indexing. This fix checks for the orphan condition and skips the
trace insert entirely, logging a warning instead.

### 2. Bridge startup race (`ef311ebb`)
Hermes gateway restarts can race with the MemOS daemon warm-up (~30s).
The default 15s poll deadline and 30s `session.open` timeout cause
cascading failures when the bridge isn't ready. This extends the
deadline to 45s, adds exponential backoff health probes, and ups the
session timeout to 60s. Double-spawn prevention via PID file singleton
guard ensures only one bridge process survives a race.

### 3. RPC loopback auth (`a459a0a5`)
The `/api/v1/rpc` session exemption was overly broad — any caller could
bypass auth. This restricts the exemption to loopback callers only
(`127.0.0.1` / `::1`).

## Testing

All three fixes have been running on three companion instances
(neuromancer, wintermute, case) for 48+ hours with zero bridge failures,
no new orphan traces, and clean RPC auth.
…y skip evolution pipel (#2074)

* fix(memos-local-plugin): actually skip evolution pipeline when lightweightMemory is enabled (#2063)

`buildPipelineSubscribers` used to attach reward / L2 / L3 / skill /
feedback subscribers unconditionally, so `algorithm.lightweightMemory.enabled: true`
only affected `flush()` — every `episode.finalized` still cascaded
through the LLM-heavy evolution chain.

Startup + periodic recovery compounded the bug: `recoverOpenEpisodesAsSessionEnd`
and `recoverDirtyClosedEpisodes` re-emitted `episode.finalized` for
legacy episodes that lack `meta.lightweightMemory === true`, producing
a large backlog of `skill_generate` / `world_model_generate` /
`policy_evolve` calls after every bridge restart.

- Gate subscriber attachment in `buildPipelineSubscribers` and return
  no-op stubs for the runner/handle shape callers depend on.
- Treat every orphan / stale / dirty-closed episode as lightweight in
  the recovery paths of `memory-core.init`, `autoFinalizeStaleTasks`,
  and `autoRescoreDirtyClosedEpisodes` when the flag is on.
- Regression coverage in `tests/unit/pipeline/lightweight-mode.test.ts`.

* fix(memos-local-plugin): address open code review findings on lightweight mode

- deps.ts: return throwLightweight(...) in rewardSubscription.runManually and
  l2.runOnce so control-flow analysis marks subsequent statements unreachable,
  matching the pattern used by every other throwing stub in the file.
- memory-core.ts (autoFinalizeStaleTasks lightweight path): write the
  lightweight meta BEFORE close() so a failing close() cannot leave an
  episode without meta.lightweightMemory=true (which would let the periodic
  non-lightweight rescan pick it up). Also rename the meta fields to
  closeReason="lightweight_stale" + closedAtMs so observability tooling does
  not mistake a periodic close for startup recovery.
- memory-core.ts (init lightweight orphan close): replace the nested ternary
  for recoveryReason with an explicit if/else.

* fix(memos-local-plugin): address round-2 open code review on init lightweight close

Three issues in the init() lightweight-orphan-close loop:

1. Ordering: close() was called BEFORE updateMeta(). If close() succeeded
   but updateMeta() threw, the episode ended up closed without
   meta.lightweightMemory=true, so autoRescoreDirtyClosedEpisodes would
   later pick it up and feed it into the reward/L2/L3 pipeline. Now the
   meta write happens first — matching the periodic stale-topic path.

2. Error isolation: the loop had no per-episode try/catch, so a single
   DB lock / constraint violation would abort the whole loop and be
   swallowed by the outer scan-level try/catch. Now each episode has its
   own try/catch that emits init.lightweight_close_error at debug.

3. Backfill semantics: legacy rows being backfilled were tagged
   closeReason="finalized" even though they were never actually
   finalized. They now get closeReason="lightweight_backfill", matching
   the existing recoveryReason="lightweight_startup_close_backfill"
   distinction so downstream analytics can tell the two apart.

---------

Co-authored-by: autodev <autodev@memtensor.local>
* docs(cn): translate remaining English text in dream.md

* chore: update github workflow stale.yaml

* feat: add configurable CJK keyword tokenization

---------

Co-authored-by: harvey_xiang <harvey_xiang22@163.com>
Co-authored-by: sonimwang <17816198144@163.com>
Co-authored-by: HarveyXiang <harvey_xiang@163.com>
Summary
- Fixes #1421.
- Add optional `inputType`, `queryInputType`, and `documentInputType`
embedding config fields.
- Route document embeddings through `documentInputType` and query
embeddings through `queryInputType`, falling back to `inputType` when a
specific value is absent.
- Forward `input_type` to OpenAI-compatible and OpenClaw host embedding
requests when configured.
- Retry the viewer embedding model probe once with `input_type: "query"`
when a provider explicitly reports that `input_type` is required.

Validation
- static embedding input_type checks passed
- npm test -- --run tests/embedding-input-type.test.ts (blocked: vitest:
not found)
- npm run build (blocked: tsc: not found)
- npm run lint (blocked: eslint: not found)
- make format (blocked: poetry: Command not found)
)

feat(memos-local): add data export endpoint and UI (#1474)

- SqliteStore.exportAll(): dump all memories/tasks/skills as JSON
- SqliteStore.exportMemoriesAsCsv(): memories-only CSV with ISO timestamps
- GET /api/export?format=json|csv endpoint in ViewerServer
- Export buttons in Settings → General tab (i18n: en + zh)
- exportData() JS helper triggers file download via <a> click

Closes #1474

Co-authored-by: zhaxi <syzsunshine219@gmail.com>
syzsunshine219 and others added 16 commits July 9, 2026 11:08
…ble probe (#1663)

## Problem

When configuring `openai_compatible` provider with an endpoint that
already includes the full API path (e.g. Zhipu AI / GLM models):

```yaml
endpoint: https://open.bigmodel.cn/api/paas/v4/chat/completions
```

The probe function would append another `/v1/chat/completions` segment,
resulting in a malformed URL:

```
https://open.bigmodel.cn/api/paas/v4/chat/completions/v1/chat/completions
```

This causes the model test to fail for any provider whose endpoint
already specifies the full path.

## Solution

Check if the endpoint already ends with the target path
(`/chat/completions` or `/embeddings`) before appending. The URL
resolution now handles three cases:

1. Endpoint ends with `/chat/completions` → use as-is
2. Endpoint ends with `/v1` → append `/chat/completions`
3. Otherwise → append `/v1/chat/completions`

The same fix was applied symmetrically to both `probeChat` and
`probeEmbedding`.

## Files Changed

- `apps/memos-local-plugin/server/routes/models.ts`

## Backward Compatibility

Fully backward compatible — existing endpoint formats (`/v1` or base
URL) continue to work as before. This only adds support for the
already-full-path case.
…ist (#1669)

Co-authored-by: zhaxi <syzsunshine219@gmail.com>
# Conflicts:
#	src/memos/api/config.py
## Description

fix: Disable thinking for qwen3 preference extractor

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

- Not applicable

## Checklist

- [x] I have performed a self-review of my own code | 我已自行检查了自己的代码
- [x] I have commented my code in hard-to-understand areas |
我已在难以理解的地方对代码进行了注释
- [x] I have added tests that prove my fix is effective or that my
feature works | 我已添加测试以证明我的修复有效或功能正常
- [ ] I have created related documentation issue/PR in
[MemOS-Docs](https://github.com/MemTensor/MemOS-Docs) (if applicable) |
我已在 [MemOS-Docs](https://github.com/MemTensor/MemOS-Docs) 中创建了相关的文档
issue/PR(如果适用)
- [x] I have linked the issue to this PR (if applicable) | 我已将 issue
链接到此 PR(如果适用)
- [x] I have mentioned the person who will review this PR | 我已提及将审查此 PR
的人

## Reviewer Checklist
- [ ] closes #xxxx (Replace xxxx with the GitHub issue number)
- [ ] Made sure Checks passed
- [ ] Tests have been provided
…rable (#1483)

* feat(memos-local): make auto-recall Phase 1 parameters configurable

Auto-recall Phase 1 was using hardcoded maxResults=10 and minScore=0.45
for the quick local search pass before LLM dedup. This makes those
values configurable via the existing recall.* config namespace:

  recall.autoRecallMaxResults  (default: 3)
  recall.autoRecallMinScore    (default: 0.50)

Lowering Phase 1 defaults from 10→3 and 0.45→0.50 improves auto-recall
precision and reduces token usage during the dedup step, especially for
users with large memory stores.

* fix: align auto-recall config with base

---------

Co-authored-by: 庸人i <yong@yongrenideMac-mini.local>
Co-authored-by: jiachengzhen <jiacz@memtensor.cn>
…DME_ZH (#2078)

* docs: rewrite README around three usage scenarios with bilingual support

- Rewrite README.md around three entry points: self-host, OpenClaw cloud plugin, local plugin
- Add Performance block with 10 benchmark scores from 2026-07-02 evaluation report
- Add "What MemOS Is For" section and decision table for scenario navigation
- Add Chinese README_ZH.md with bidirectional language switch links
- Use official slogan "Intelligence begins with memory" as tagline

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs: restore visual layout and restructure Quick Start sections

- Restore top banner GIF, logo, title, shield badges from original README
- Replace performance highlights list with official slogan
- Add Performance block with 10 benchmark scores
- Restructure Quick Start: Self-Host and Plugin as peer ### sections,
  Cloud Plugin and Local Plugin as #### subsections under Plugin
- Remove redundant Docker note from Local Plugin section
- Sync structure between English and Chinese versions

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs: restore lost images in README header, plugin, and community sections

- Restore centered logo (with link to memos.openmem.net), title, and shield
  badges (Docs, ArXiv, Follow, Discord, Resources) in both READMEs
- Restore plugin banner image under the MemOS Plugin section header
- Restore WeChat QR code under the Community section
- Sync structure between English and Chinese versions

* docs: add local plugin install commands and restructure README overview

- Add macOS/Linux and Windows install commands for memos-local-plugin,
  with a prerequisites note (Node.js + an installed OpenClaw/Hermes)
- Use robust download-then-run form on Windows instead of irm|iex so
  errors stay visible and the script dir resolves correctly
- Add "MemOS: Memory Operating System" overview section (Key Features + News)
- Move plugin banner to the top; simplify the tagline
- Keep English and Chinese versions structurally in sync

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs: restore header logo, badges, banner, and QR code in README_ZH

Bring the Chinese README visually in sync with the English version:
centered logo (linked), shield badges, top plugin banner, and the
WeChat QR code in the Community section.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs: add Cloud API (Hosted) quick-start scenario to README

Add the managed MemOS Cloud API as a fourth entry point in the Quick
Start of both README.md and README_ZH.md: a comparison-table column,
API-key setup, an add/search code example, and next-step doc links.
Mirrors the Cloud API section from upstream main.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: sunqi <sunqi@memtensor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: zhaxi <syzsunshine219@gmail.com>
* fix: quarantine stale sqlite native binding

* fix(openclaw): report unlink failure (not rename) and dedupe quarantine paths

OCR review on #2049 surfaced two real bugs in quarantineNativeBinding:

1. The inner 'catch {}' block had no binding, so 'reason' reported the
   outer rename failure instead of the unlink failure when both fail.
2. The quarantine path used a Date.now()-only suffix, which collides
   if the helper is called twice in the same millisecond (e.g. a rapid
   postinstall retry). The second call's renameSync would silently
   overwrite the first quarantined file.

Also injects 'path' and 'random' as optional parameters so the helper
is fully testable with stubs. Adds two regression tests covering the
unlink-error-capture fix and the timestamp-collision guarantee.

* fix: make native binding quarantine paths deterministic

---------

Co-authored-by: Memtensor-AI <project@memtensor.cn>
Co-authored-by: Ziyang Guo <121015044+RunMarshal@users.noreply.github.com>
Co-authored-by: zhaxi <syzsunshine219@gmail.com>
Co-authored-by: jiachengzhen <jiacz@memtensor.cn>
Fixes #1380.

Summary
- Tighten the skill creation evaluator prompt so low-reuse tasks are
rejected before skill generation.
- Add explicit filters for simple tool installation plus basic config,
exploratory revert/abandon outcomes, and discussion/comparison-only
outputs.
- Add the requested experienced-developer self-check before returning
shouldGenerate=true.
- Reinforce generator guidance to prefer reusable patterns, abstract
diagnostic mistakes, and keep project/version-specific details as
examples unless required.
- Add a prompt-policy regression test for the new evaluator and
generator guidance.

Validation
- Direct Node prompt-policy string check passed.
- Attempted: npm test -- --run tests/skill-prompt-policy.test.ts;
blocked because vitest is not installed in this worktree.
- Attempted: npm run build; blocked because tsc is not installed in this
worktree.
- Attempted: make format; blocked because poetry is not installed in
this environment.
* feat(l3): dedicated l3Llm config slot for abstraction pass

L3 abstraction runs on the main `llm`, which on cheap models
(gemini-2.5-flash-lite) over-extracts and truncates the JSON,
producing "'constraints'/'inference' must be an array" failures —
Violet logged 180 such failures and produced only 2 world-model
facts total.

Add an `l3Llm` config slot (same SkillEvolverSchema shape as
`skillEvolver`). Blank inherits the main `llm` (zero behavior
change); set explicitly to run the clustering → world-model pass
on a stronger model. L3 is async/off the turn-response path, so
a slower-but-correct model has no impact on companion latency.

Wiring mirrors skillEvolver: schema + blank default + secret
redaction + bootstrap client + PipelineDeps/PipelineHandle field
+ orchestrator handle passthrough, consumed at the L3 subscriber
attach (`deps.l3Llm ?? deps.llm`).

* Fix l3Llm orchestrator test helper import

* fix: resolve l3 llm config conflict

* fix: satisfy ruff for hermes bridge reconnect

---------

Co-authored-by: jiachengzhen <jiacz@memtensor.cn>
Co-authored-by: zhaxi <syzsunshine219@gmail.com>
fix: publish compiled openclaw plugin entry

Co-authored-by: Ziyang Guo <121015044+RerankerGuo@users.noreply.github.com>
…able vector scan (scan (#2077)

* fix(local-plugin): stop unbounded trace re-insertion + 100% CPU vector scan (#2076)

Two coupled root causes made the gateway Node process pin one CPU core
at 100% with 4.2 GB RSS on startup for 40+ minutes on a 518k-row DB.

Bug #1 — synchronous full-table vector scan (`core/storage/vector.ts`):
`scanAndTopK` used `db.prepare(sql).all(params)`, materialising up to
`hardCap` (default 100_000) rows in one synchronous step. Each row
carries a multi-KB vector BLOB, so peak RSS was `O(hardCap × dim)` and
the event loop was blocked while `topKCosine` walked the whole array.
Fix: stream via `.iterate()`, maintain the top-K min-heap on hits
directly (only k entries + one just-decoded vector live in JS memory
at a time), and lower the default `hardCap` to 5_000 as a safer default
(all production callers pass their own value explicitly).

Bug #2 — dedup pagination cap (`core/capture/capture.ts`):
The four dedup call sites in `runLite` / `runLightweight` /
`runReflect` / `persistRows` all used `tracesRepo.list({ episodeId })`,
which is paginated and silently truncates to 500 rows via
`_helpers.ts::clampLimit`. Once an episode exceeded 500 traces the
older rows became invisible to dedup and the tail was re-inserted
on every cycle. In the reporter's DB this had grown the `traces`
table to 518_375 rows of which 84% were exact duplicates by
`(episode_id, turn_id, user_text, agent_text, tool_calls_json)`.
Fix: add `tracesRepo.listAllForEpisode(episodeId)`, an uncapped read
ordered by `ts ASC`, and switch the four dedup call sites to it.
Every other paginated `list({ episodeId, limit })` caller keeps its
explicit page-size contract.

Tests
  - `tests/unit/storage/traces-listall.test.ts` — 4 cases: 750-row
    episode returns in full, strict episode scoping, empty episode,
    `ts ASC` ordering.
  - `tests/unit/storage/vector-stream.test.ts` — 3 cases: parity
    with brute-force top-K on live DB, streaming keeps only top-K in
    memory yet still considers every row within the cap, explicit
    `hardCap` still truncates the candidate window.
  - Full plugin unit suite: 1128 pass, 1 skipped. Two pre-existing
    failures on `dev-v2.0.23` (startup-recovery source-string check,
    migrator schema-drift) are unrelated to this fix.

Closes #2076

* refactor(local-plugin): apply #2077 open code review fixes

Address the five findings from open code review on PR #2077:

1. `traces.ts::listAllForEpisode` was hydrating all 25 columns including
   `vec_summary`/`vec_action` BLOBs while dedup callers only ever read
   the identity fields. Added `listDedupRowsForEpisode` — a streaming
   sibling that projects only the five dedup columns
   (ts/turn_id/user_text/agent_text/tool_calls_json) and uses
   `.iterate()` so peak RSS scales with scalar payload rather than
   total embedding footprint. `listAllForEpisode` kept as-is for
   `runReflect`, which still needs the full row (id/tags/vecSummary/
   vecAction).

2. `vector.ts::scanAndTopK` streaming loop silently `continue`d on
   dimension mismatches, losing the `search.dim_mismatch` warning the
   old `topKCosine` path emitted. Restored the warn log with
   {expected,got,rowId} — the primary operator signal for detecting
   schema drift (re-embedding with a new model dimension) in
   production. Empty vectors keep the silent-skip semantics as before.

3. `vector.ts::scanAndTopK` had the meta-object construction
   duplicated verbatim in both heap-push branches. Hoisted into a
   single `buildMeta()` closure above the branch so future edits to
   the projection cannot drift between the two paths.

4. `capture.ts::persistRows` unconditionally called
   `listAllForEpisode` even when its caller (`runLite` /
   `runLightweight` / `runReflect`) had just scanned the same
   episode. Added an optional `existingSignatures?: Set<string>` param;
   all three call-sites now pass the pre-built signature set derived
   from their own dedup scan, eliminating the second full-episode
   scan. The set is cloned inside `persistRows` so the intra-batch
   dedup doesn't leak new signatures back to the caller.

5. `capture.ts::runLite` (and `runLightweight`) previously fetched
   full `TraceRow[]` via `listAllForEpisode` and threw everything
   away except the `ts` / `turnId` field. Switched both to
   `listDedupRowsForEpisode` so no BLOBs load for the extraction
   dedup pass.

`traceIdentitySignature` widened to `Pick<TraceRow, "toolCalls" |
"turnId" | "ts" | "agentText" | "userText">` so it can accept either
`TraceRow` (runReflect path) or the narrow `TraceDedupRow` produced
by the streaming helper.

Tests
  - tests/unit/storage/traces-listall.test.ts — 4 new cases pin the
    narrow-projection contract (fields returned, empty episode,
    750-row uncapped stream, strict episode scoping + ts ASC).
  - tests/unit/storage/vector-stream.test.ts — 2 new cases pin the
    dim-mismatch warning fires for non-empty mismatched vectors and
    stays silent for zero-length vectors.

Verification
  - `npx tsc -p tsconfig.json --noEmit` → clean.
  - `npx vitest run tests/unit/storage/traces-listall.test.ts
     tests/unit/storage/vector-stream.test.ts` → 13/13 pass.
  - `npx vitest run tests/unit/capture` → 110/110 pass.
  - Full `npx vitest run tests/unit` → 1134/1137 pass; the two
    failures (storage/migrator, startup-recovery) reproduce on the
    unmodified PR head, so they are pre-existing and unrelated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(local-plugin): apply #2077 open code review fixes (round 2)

Second-pass open-code-review fixes on top of 3e25bc3. All three are
minimal-diff / non-behavior-changing.

1. `vector.ts::scanAndTopK` was building the `buildMeta` closure
   inside the per-row streaming loop. On a hot search over the full
   `hardCap` this allocated one closure per iteration — pure GC
   pressure since only the two heap-push branches consume it.
   Hoisted the projection above the loop as a plain function taking
   the current row as an argument. Two variants pre-selected once
   based on `selectExtra.length` so filtered-out rows still pay
   nothing.

2. `traces.ts::listDedupRowsForEpisode` JSDoc claimed the helper
   "streams via `.iterate()` so peak memory scales with the scalar
   payload". That was misleading: the method uses `stmt.iterate()`
   internally but still accumulates every row into a `TraceDedupRow[]`
   before returning. The genuine saving is the narrow scalar-only
   projection (never touches `vec_summary` / `vec_action`), not
   streaming. Rewrote the JSDoc to describe the real cost model and
   name the `.iterate()` benefit accurately (avoids an intermediate
   `.all()` allocation, does not stream to caller).

3. `traces.ts::listAllForEpisode` JSDoc said "all hot fields required
   by dedup are projected, so a caller that only needs `ts`, `turnId`,
   or the identity signature can still iterate at full speed". That
   accidentally invited dedup callers back onto this method, which
   loads every BLOB column. Rewrote the JSDoc to state explicitly
   that vec_summary/vec_action BLOBs are loaded and redirect
   dedup-only callers to `listDedupRowsForEpisode`. Preserved the
   #2076 context on why the uncapped variant exists.

No behavioral change. No test changes needed.

Verification
  - `npx tsc -p tsconfig.json --noEmit` → clean.
  - `npx vitest run tests/unit/storage/traces-listall.test.ts \
     tests/unit/storage/vector-stream.test.ts` → 13/13 pass.
  - `npx vitest run tests/unit/capture` → 110/110 pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: avoid #2077 capture merge conflict

---------

Co-authored-by: MemOS AutoDev <autodev@memtensor.local>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: jiachengzhen <jiacz@memtensor.cn>
@CarltonXiang CarltonXiang changed the title Dev v2.0.23 release: merge dev-v2.0.23 into main Jul 9, 2026
@CarltonXiang CarltonXiang merged commit 583b07b into main Jul 9, 2026
32 checks passed
@CarltonXiang CarltonXiang deleted the dev-v2.0.23 branch July 9, 2026 07:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.