Skip to content

fix: remove 500-row cap that truncated viewer count displays#1954

Merged
syzsunshine219 merged 9 commits into
MemTensor:dev-v2.0.25from
chiefmojo:fix/viewer-count-cap
Jul 23, 2026
Merged

fix: remove 500-row cap that truncated viewer count displays#1954
syzsunshine219 merged 9 commits into
MemTensor:dev-v2.0.25from
chiefmojo:fix/viewer-count-cap

Conversation

@chiefmojo

Copy link
Copy Markdown
Contributor

Problem

clampLimit() in _helpers.ts hard-caps query limits at 500 rows. The viewer's trace and episode count displays fetch the first N rows to count totals, so any store with more than 500 items shows a truncated count.

Fix

Raise the cap from 500 to 100,000. This is a safety guard against pathological inputs, not a query-size policy — reasonable operators setting limit: 10_000 for bulk exports should not be silently truncated.

export function clampLimit(n: number): number {
  if (!Number.isFinite(n) || n <= 0) return 50;
  return Math.min(Math.trunc(n), 100_000);
}

clampLimit() in _helpers.ts capped all repo list() calls at 500,
silently truncating skill/policy/world-model counts everywhere they
were derived from array length rather than a COUNT(*) query.

- Raise clampLimit cap 500 → 100,000 so metrics() and other internal
  analytics can read full datasets (fixes Analytics tab)
- Rewrite /api/v1/overview to use countSkills/countPolicies/
  countWorldModels/countEpisodes instead of list+length, making
  Overview counts correct regardless of scale (fixes Overview tab)
- Align countSkills to use limit:100_000 consistent with other
  count methods
@Memtensor-AI
Memtensor-AI changed the base branch from dev-20260604-v2.0.19 to dev-v2.0.22 July 1, 2026 13:15
@Memtensor-AI

Copy link
Copy Markdown
Collaborator

Automated Test Results: FAILED

Cloud test-engine rerun against dev-v2.0.22 failed.

  • Run: tr-e1b6e067-ca4 on cloud test-engine 10011

  • Verdict: fail_code_bug

  • Summary: Tests failed. Failed cases: test -- tests/unit/pipeline/memory-core.test.ts tests/unit/server/http.test.ts

  • memos_local_plugin/unit: 102 passed, 1 failed, 0 skipped

Failed cases:

  • test -- tests/unit/pipeline/memory-core.test.ts tests/unit/server/http.test.ts: ry shape the viewer expects
    AssertionError: expected 500 to be 200 // Object.is equality

  • Expected

  • Received
  • 200
  • 500

❯ tests/unit/server/http.test.ts:834:22
832| it("GET /api/v1/overview returns the summary shape the viewer expect…
833| const r = await fetch(${handle.url}/api/v1/overview);
834| expect(r.status).toBe(200);
| ^
835| const body = (await r.json()) as {
836| ok?: boolean;

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯

Do not merge until this is fixed and cloud tests pass.

@CarltonXiang
CarltonXiang deleted the branch MemTensor:dev-v2.0.25 July 3, 2026 07:25
@syzsunshine219 syzsunshine219 reopened this Jul 3, 2026
@syzsunshine219
syzsunshine219 changed the base branch from dev-v2.0.22 to main July 3, 2026 08:21
@Memtensor-AI Memtensor-AI added area:plugin OpenClaw & Hermes area:memory 记忆存储、检索、更新、召回逻辑 labels Jul 8, 2026
@Memtensor-AI
Memtensor-AI requested a review from bittergreen July 8, 2026 11:44
@Memtensor-AI Memtensor-AI added area:database graph_db + vector_db | 图数据库与向量数据库 and removed area:memory 记忆存储、检索、更新、召回逻辑 labels Jul 9, 2026
@Memtensor-AI Memtensor-AI assigned whipser030 and wustzdy and unassigned bittergreen Jul 9, 2026
@Memtensor-AI Memtensor-AI removed the area:database graph_db + vector_db | 图数据库与向量数据库 label Jul 9, 2026
chiefmojo and others added 4 commits July 23, 2026 17:32
* Fix MemTensor#1540: fix: (MemTensor#1824)

docs(memos-local-plugin): clarify install path and stale dir names (MemTensor#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
MemTensor#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 MemTensor#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>

* Fix MemTensor#1888: [Bug] test_system_parser.py: SystemParser.__init__() got an unexpected keyword a (MemTensor#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 MemTensor#1888

Co-authored-by: MemOS AutoDev <autodev@memos.ai>
Co-authored-by: Matthew <heimixiaozhuang@zju.edu.cn>

* Fix MemTensor#1525: [Bug] clean_json_response crashes with cryptic AttributeError when given None (MemTensor#1884)

* test: add comprehensive tests for clean_json_response (issue MemTensor#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>

* Fix MemTensor#1901: share_cube_with_user passes swapped args to _validate_cube_access — fails for ev (MemTensor#1903)

fix: validate current user not target in share_cube_with_user (MemTensor#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 MemTensor#1901

Co-authored-by: MemOS AutoDev Bot <autodev@memtensor.local>
Co-authored-by: Matthew <heimixiaozhuang@zju.edu.cn>

* feat(memos): chunk batch reflection scoring

* Fix MemTensor#1897: bug(memos-local-plugin): Hermes background model status checks can trigger paid  (MemTensor#1899)

* Fix MemTensor#1897: fix(memos-local-plugin): add LLM circuit breaker for terminal provider errors

Issue MemTensor#1897 reported ~12,900 paid LLM requests in 24 h on Hermes
against a DeepSeek key with insufficient balance. The local
`system_model_status` row count (12,900) closely tracked the
provider-side `request_count` (11,344) for the same billing window.
The naming is misleading: `system_model_status` is not a health probe;
it is the audit row written once per LLM call (ok / fallback / error)
inside `core/llm/client.ts`. With no circuit breaker, every pipeline
subscriber (capture / session-relation / reward / L2 / L3 / skill /
retrieval LLM filter / world-model) kept firing on every turn / closed
episode / induction, generating one paid request each.

Add a per-`LlmClient` circuit breaker:

- Trips on terminal errors: HTTP 401/402/403 or messages containing
  `insufficient balance` / `invalid api key` / `unauthorized` /
  `account suspended` / `billing`.
- Open: short-circuits subsequent calls inside the facade without
  contacting the provider. Throws `MemosError(LLM_UNAVAILABLE)` with
  `details.circuitOpen=true` so existing catch blocks still work.
- Half-open after cool-down (default 5 min, configurable, min 30 s):
  next call probes the provider; success closes the breaker, terminal
  failure re-opens it for another cool-down.
- Host fallback rescues a call without tripping the breaker —
  fallback exists precisely to keep going when the primary is down.
- Coalesces `system_model_status="circuit_open"` audit rows to at
  most one per ~25 s while the breaker stays open, so we don't
  replace paid spam with audit-row spam.
- Exposes `circuitOpen` / `circuitOpenUntil` / `circuitOpenedReason`
  via `LlmClientStats` for the Overview viewer card.
- Enabled by default; legacy behaviour available via
  `circuitBreaker.enabled = false`.

Tests: 9 new vitest cases under `tests/unit/llm/client.test.ts`
covering trip on 402, trip on "insufficient balance" message, no
trip on generic transient, coalescing, half-open close on success,
host-fallback rescues without trip, disabled mode, stats fields,
and re-open on terminal probe failure. All 59 LLM and 28 pipeline
tests pass; `tsc --noEmit` clean.

Out of scope (tracked separately): 429 `Retry-After` handling
(issue MemTensor#1620), per-tool rate limits, daily budget caps.

* Fix LLM breaker with host fallback

---------

Co-authored-by: autodev-bot <autodev@memtensor.local>
Co-authored-by: Jiang <33757498+hijzy@users.noreply.github.com>
Co-authored-by: GU TIANCHUN <96930846+TianchunGu@users.noreply.github.com>
Co-authored-by: Dubberman <48425266+whipser030@users.noreply.github.com>

---------

Co-authored-by: Memtensor-AI <project@memtensor.cn>
Co-authored-by: MemOS AutoDev <autodev@memtensor.ai>
Co-authored-by: Matthew <heimixiaozhuang@zju.edu.cn>
Co-authored-by: MemOS AutoDev <autodev@memos.ai>
Co-authored-by: MemOS AutoDev Bot <autodev@memtensor.local>
Co-authored-by: Jiang <33757498+hijzy@users.noreply.github.com>
Co-authored-by: GU TIANCHUN <96930846+TianchunGu@users.noreply.github.com>
Co-authored-by: Dubberman <48425266+whipser030@users.noreply.github.com>
…nsor#2145)

This reverts commit 21a27ed.

Co-authored-by: jiachengzhen <jiacz@memtensor.cn>
@Memtensor-AI Memtensor-AI added the status:in-progress Someone or AI is working on it | 人工或 AI 正在处理 label Jul 23, 2026
@Memtensor-AI
Memtensor-AI requested a review from hijzy July 23, 2026 10:27
@Memtensor-AI
Memtensor-AI requested a review from whipser030 July 23, 2026 10:27
@syzsunshine219

Copy link
Copy Markdown
Collaborator

Automated Test Results: PASSED

Cloud test-engine rerun against the current PR head and latest main completed successfully after fixing the test stub.

  • Passing run: tr-783ba09b-a4a on cloud test-engine 10012
  • Ref: refs/pull/1954/head merged with latest main before execution
  • Scope: memos_local_plugin/unit
  • Result: 107 passed, 0 failed, 0 skipped
  • Duration: 12s

The preceding rerun (tr-3c29f91b-182) reproduced the prior failure: /api/v1/overview returned 500 because the HTTP test MemoryCore stub did not implement the newly used countPolicies() and countWorldModels() methods. Commit 27c916ed adds those two stubs; production code is unchanged by the test fix. Manual code review is still required before merge.

@Memtensor-AI

Memtensor-AI commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

🤖 Open Code Review

Target: PR #1954
Task: 6f9badc340848847
Base: dev-v2.0.25
Head: fix/viewer-count-cap

🔍 OpenCodeReview found 4 issue(s) in this PR.


1. apps/memos-local-plugin/core/pipeline/memory-core.ts (L3767-L3769)

Replacing one arbitrary hardcoded ceiling (5_000) with another (100_000) still silently under-counts when the actual number of skills exceeds the limit. countSkills is supposed to return an accurate total, but it will return at most 100_000 regardless of true data size, making the count unreliable at scale.

Additionally, the sibling function listSkills (just above, line 3753) still uses limit: 5_000, so the count returned by countSkills will exceed the number of rows actually retrievable via listSkills for large datasets — creating a visible pagination inconsistency for callers.

Consider two targeted fixes:

  1. For countSkills: use a dedicated repository method that performs a true DB-level count (e.g., handle.repos.skills.count(...)) instead of fetching all rows into memory and calling .length. If no such method exists, at minimum paginate/loop until exhausted rather than relying on a single large limit.
  2. For consistency: align the limit in listSkills with the actual maximum supported by the repo layer, or document clearly that both functions are bounded by their respective caps.
💡 Suggested Change

Before:

    return handle.repos.skills.list({ status: input?.status, limit: 100_000 }).filter((r) =>
      (input?.includeAllNamespaces || visibleToCurrent(r)) && matchesNamespaceFilter(r, input)
    ).length;

After:

    // TODO: replace with a DB-level count query to avoid loading all rows into memory
    // and to guarantee accuracy beyond any fixed limit.
    // e.g., return handle.repos.skills.count({ status: input?.status, ... });
    return handle.repos.skills.list({ status: input?.status, limit: 100_000 }).filter((r) =>
      (input?.includeAllNamespaces || visibleToCurrent(r)) && matchesNamespaceFilter(r, input)
    ).length;

2. apps/memos-local-plugin/core/pipeline/memory-core.ts (L3767)

Loading up to 100,000 full skill rows into memory on every call to countSkills purely to obtain a .length is a significant performance concern. At scale this causes large heap allocations and slows down every count operation. A DB-level COUNT query or a streaming approach should be used instead.


3. apps/memos-local-plugin/server/routes/overview.ts (L50-L73)

The countSkills implementation in memory-core.ts (revealed by the companion diff) still performs a full table scan under the hood: handle.repos.skills.list({ status, limit: 100_000 }).filter(...).length. By calling countSkills three times — once per status — this code issues three separate full scans of the skills table (up to 300,000 row fetches in the worst case), and similarly three scans for policies.

A single call without a status filter (i.e., countSkills({ includeAllNamespaces: true }) returning total, then iterating once) would be far cheaper. Even better, a single listSkills({ limit: 100_000, includeAllNamespaces: true }) call followed by in-memory grouping — analogous to the old approach — would read the table once and produce all three counts. The refactor inadvertently triples the I/O cost for skills and policies.

Consider consolidating into a single count call or adding a batched count API before landing this.

💡 Suggested Change

Before:

      deps.core.countSkills({
        status: "active",
        includeAllNamespaces: true,
      }),
      deps.core.countSkills({
        status: "candidate",
        includeAllNamespaces: true,
      }),
      deps.core.countSkills({
        status: "archived",
        includeAllNamespaces: true,
      }),
      deps.core.countPolicies({
        status: "active",
        includeAllNamespaces: true,
      }),
      deps.core.countPolicies({
        status: "candidate",
        includeAllNamespaces: true,
      }),
      deps.core.countPolicies({
        status: "archived",
        includeAllNamespaces: true,
      }),

After:

      // Single unfiltered count covers all statuses; then one filtered
      // scan for the per-status breakdown avoids triple table scans.
      deps.core.countSkills({ includeAllNamespaces: true }),
      deps.core.listSkills({ limit: 100_000, includeAllNamespaces: true }),
      deps.core.countPolicies({ includeAllNamespaces: true }),
      deps.core.listPolicies({ limit: 100_000, includeAllNamespaces: true }),

4. apps/memos-local-plugin/server/routes/overview.ts (L75-L77)

The comment reads "cheaper than a dedicated count RPC and already cached by the core", which was accurate in the old code where metrics() was the only count-like call and the surrounding calls were bulk list fetches. In the refactored code, every other call in the Promise.all IS a dedicated count RPC, so the contrast the comment draws no longer makes sense to a reader. The phrase "cheaper than a dedicated count RPC" is now actively misleading in this context.

Update the comment to describe only what metrics.total represents (the grand total of traces), without the now-false efficiency comparison.

💡 Suggested Change

Before:

      // `metrics.total` is the grand total of traces — cheaper than a
      // dedicated count RPC and already cached by the core.
      deps.core.metrics({ days: 1, includeAllNamespaces: true }),

After:

      // `metrics.total` is the grand total of trace turns shown on the
      // Overview page; `metrics()` also provides write-today / session
      // counts that may be used in the future.
      deps.core.metrics({ days: 1, includeAllNamespaces: true }),

Generated by cloud-assistant via Open Code Review.

@Memtensor-AI

Copy link
Copy Markdown
Collaborator

✅ Automated Test Results: PASSED

All tests passed (107/107 executed). memos_local_plugin/unit: 107/107. Duration: 11s

Branch: fix/viewer-count-cap

@Memtensor-AI Memtensor-AI added status:ready Ready for implementation; waiting for assignee or AI dispatch | 可进入实现,等待认领或派发 and removed status:in-progress Someone or AI is working on it | 人工或 AI 正在处理 labels Jul 23, 2026
@syzsunshine219
syzsunshine219 changed the base branch from main to dev-v2.0.25 July 23, 2026 10:44
@Memtensor-AI Memtensor-AI added status:in-progress Someone or AI is working on it | 人工或 AI 正在处理 and removed status:ready Ready for implementation; waiting for assignee or AI dispatch | 可进入实现,等待认领或派发 labels Jul 23, 2026
@syzsunshine219
syzsunshine219 merged commit 63ddb97 into MemTensor:dev-v2.0.25 Jul 23, 2026
0 of 18 checks passed
@Memtensor-AI

Copy link
Copy Markdown
Collaborator

⚠️ Automated Test Results: NO TEST SCOPE

Automated tests were not run because the changed files do not map to an executable test scope.

Details: No executable test scope maps to the changed files. Automated tests were not run; add env.yaml source_mapping + execution for this path family, then rerun. Changed files: (none detected)
Manual review or env.yaml source_mapping/execution coverage is required before merge.

Branch: fix/viewer-count-cap

CarltonXiang added a commit that referenced this pull request Jul 24, 2026
* ci: automate OpenClaw local plugin release notes (#2129)

Generate evidence-backed OpenClaw local plugin release notes through the configured draft service.

Add dry-run and publish workflow safeguards, retry/repair validation, prerelease handling, failure reporting hooks, redacted inspection artifacts, and focused local plugin test coverage.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix: Adjust feedback update review flow & using independent

* refactor: unify LLM provider environment config

# Conflicts:
#	examples/basic_modules/llm.py
#	src/memos/mem_reader/multi_modal_struct.py

* fix: fix log for embedding models

* feat(api):update SDK version number

* fix: fix tests

* fix: preserve text when downgrading feedback updates

* feat(api):update SDK version number

* fix: type parser factory config as base parser config

* fix: Open Code Review problems fix

* feat: OpenRouter provider routing and reasoning control (#1958)

* feat(memos): add OpenRouter provider routing

* fix(memos): route OpenRouter prefs through all slots

* Fix OpenRouter reasoning config types

* fix(openrouter): harden routing detection

* fix(openrouter): harden review follow-ups

* fix(openrouter): preserve provider defaults

---------

Co-authored-by: jiachengzhen <jiacz@memtensor.cn>
Co-authored-by: shinetata <149466187+shinetata@users.noreply.github.com>

* refactor: summarize search and embedding logs

* perf: cache and coalesce embedding requests

* fix: summarize cosine reranker logs

* perf: optimize PolarDB retrieval round trips

* perf: prune MMR candidates by memory bucket

* Fix #1611: classifyTopic fails when summarizer uses Anthropic endpoint (Kimi Code) (#2133)

* fix(openclaw): route classifyTopic + arbitrateTopicSplit per provider (#1611)

`Summarizer.classifyTopic` and `Summarizer.arbitrateTopicSplit` always
dispatched to the OpenAI implementation regardless of the summarizer's
configured provider. When configured against an Anthropic-only endpoint
(e.g. Kimi Code's `/coding/v1/messages`), every call returned 404 because
the OpenAI helper appended `/chat/completions` to a URL that does not
exist — wasting tokens, polluting logs, adding event-loop latency.

Fix (Option A from the issue):

- Add native classifyTopic{Anthropic,Gemini,Bedrock} and
  arbitrateTopicSplit{Anthropic,Gemini,Bedrock} in the three provider
  files, mirroring the shape of the existing judgeNewTopic* helpers.
- Re-export TOPIC_CLASSIFIER_PROMPT / TOPIC_ARBITRATION_PROMPT from
  openai.ts so all providers share prompt strings and parseTopicClassifyResult
  remains the single JSON parser — no behavioral drift across providers.
- Rewire callTopicClassifier / callTopicArbitration switch statements
  so the anthropic / gemini / bedrock arms route to their native
  implementations instead of falling through to the OpenAI helpers.
- Bedrock helpers require cfg.endpoint (throws
  `Bedrock topic-classifier|arbitration requires 'endpoint'`) matching
  every other Bedrock helper in the file.
- Mirror every edit into packages/memos-core/src/ingest/providers/ so
  the byte-similar sibling tree cannot silently regress the bug after
  the next sync.

Tests: new tests/topic-classifier-dispatch.test.ts (8 cases) stubs
globalThis.fetch and asserts each provider hits the correct URL + body
shape; includes a regression case where an Anthropic 404 surfaces an
`Anthropic topic-classifier failed` error rather than the old
`OpenAI topic-classifier failed` — the exact string from issue #1611.
TDD baseline: 7/8 fail against buggy code. Post-fix: 8/8 pass. Existing
tests/topic-judge-minimax-1315.test.ts (5/5) continues to pass.

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

* fix(openclaw): harden topic-classifier/arbitrator providers per OCR review

Address code-review findings on the topic-classifier + arbitrator dispatch
helpers added in #2133 (issue #1611). Applied to both
apps/memos-local-openclaw and packages/memos-core (byte-similar mirrors).

Correctness / security fixes:
- anthropic: validate cfg.apiKey up front instead of silently sending "";
  move cfg.headers spread BEFORE x-api-key + anthropic-version so a
  misconfigured header cannot silently overwrite the credential
- anthropic: guard content parsing — treat missing/non-array json.content
  as [] to avoid TypeError on unexpected API payloads
- anthropic: reduce arbitrateTopicSplit max_tokens from 60 to 10 to match
  arbitrateTopicSplitOpenAI (single-word NEW/SAME reply)
- gemini: validate cfg.apiKey up front; construct URL via new URL() +
  URLSearchParams so a caller-supplied endpoint that already contains a
  query string ("?version=v1beta") does not produce malformed
  "…?version=v1beta?key=…"
- bedrock: emit warn log when arbitrateTopicSplit sees empty or
  unexpected text so silent bias toward SAME becomes visible at normal
  log levels (production usually suppresses debug)

Intentionally not applied (out-of-scope refactors that would touch
pre-existing helpers not in this PR): DRY-extract shared low-level
callers for anthropic/gemini/bedrock (findings #4/#7/#10/#14) and the
DEFAULT_BEDROCK_MODEL constant (finding #11).

Existing tests unchanged: tests/topic-classifier-dispatch.test.ts (8/8)
and tests/topic-judge-minimax-1315.test.ts (5/5) still pass. The new
guards use apiKey values already provided by every test case, so no
test edits were required.

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

* fix(openclaw): address remaining OCR round-2 findings on topic-classifier providers

Round 1 (commit 646c28a) did not fully converge OCR review. Round 2 targets
the 2 repeated findings and 10 newly detected ones without expanding scope
beyond the classifier/arbitration functions added by PR #2133.

Bedrock (packages/memos-core + apps/memos-local-openclaw):
- Fix TRUE MISS from Round 1: `arbitrateTopicSplitBedrock` was still using
  `maxTokens: 60` while Anthropic's arbitrator was already lowered to 10.
  Aligned to `maxTokens: 10`.
- Introduce `DEFAULT_BEDROCK_TOPIC_MODEL` constant scoped to the two new
  functions only (`classifyTopicBedrock`, `arbitrateTopicSplitBedrock`).
  Pre-existing helpers (`judgeDedupBedrock`, `summarizeBedrock`, etc.) keep
  their inline default to stay out of scope.
- Extract `bedrockConverseTopic()` shared transport used by the two new
  functions to eliminate the DRY duplication OCR flagged.

Gemini (packages/memos-core + apps/memos-local-openclaw):
- Add empty/unexpected-response `log.warn` branches to
  `arbitrateTopicSplitGemini`, mirroring the Bedrock arbitrator's
  defensive handling (Round 1 covered Bedrock only).
- Extract `callGeminiTopic()` shared transport used by the two new
  functions to eliminate the DRY duplication OCR flagged.
- Rejected escalating the empty-response case to `throw`: throwing would
  cascade through `Summarizer.tryChain` and burn tokens on every fallback;
  warn+default-to-SAME is the safer choice for a boundary detector.

Anthropic (packages/memos-core + apps/memos-local-openclaw):
- Extract `callAnthropicMessagesTopic()` shared transport used by the two
  new functions to eliminate the DRY duplication OCR flagged. Header spread
  order is preserved verbatim: `...cfg.headers` comes BEFORE the credential
  and `anthropic-version` so user-supplied headers cannot override them
  (this was the Round 1 security fix — do not undo).

Pre-existing helpers in each provider are intentionally untouched to keep
the diff minimum-scoped to PR #2133's classifier/arbitrator additions.

Verification (executed in apps/memos-local-openclaw):
- `./node_modules/.bin/vitest run tests/topic-classifier-dispatch.test.ts \\
  tests/topic-judge-minimax-1315.test.ts` → 13/13 passed
- `./node_modules/.bin/tsc --noEmit` on the 3 modified provider files → 0 errors
- All 6 files (3 apps + 3 packages/memos-core mirrors) are byte-identical

---------

Co-authored-by: MemOS AutoDev <autodev@memtensor.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix #2131: [Bug] memos-local-plugin : chunks table never created — causes viewer dashboard  (#2132)

* fix: viewer dashboard drifts to zero after a namespace flip (#2131)

The viewer's Overview/metrics/traces/session routes filtered reads
through the core's mutable activeNamespace, which every turn/session
rewrites from caller context hints (openclaw: profileId = ctx.agentId).
When the gateway session or a sub-agent turn flipped the profile, all
historical rows failed the visibility clauses and dashboard counts
collapsed to zero.

The reported root cause (missing chunks table) is a misdiagnosis: the
only 2.0 reference to chunks is the legacy 1.0-import reader in
server/routes/migrate.ts; the live pipeline queries traces/episodes/
sessions with vectors stored in BLOB columns, so no schema change is
needed.

Fix: complete the existing includeAllNamespaces viewer convention
(already used by diag.ts/session.ts/memory.ts) — add the option to
core.metrics() and core.listEpisodes(), and pass it from overview.ts,
metrics.ts, trace.ts and session.ts. Scoped reads and turn-time
retrieval isolation are unchanged; explicit owner filters still narrow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: align listApiLogs namespace scoping with viewer metrics feeds

Follow-up to #2131 (OCR round 1/2):

- listApiLogs: accept includeAllNamespaces in the contract and core
  facade (contract symmetry with listTraces/listSkills/listPolicies);
  metrics/tools now passes it explicitly so both feeds of the bump()
  aggregation are scoped identically
- metrics(): document that the traces fetch feeding sessions/
  writesToday/embeddings/dailyWrites is intentionally cross-namespace;
  only totalTurns respects includeAllNamespaces
- listEpisodes: apply limit/offset after the visibility filter on the
  namespace-scoped path so pages are not silently under-filled by
  other namespaces' rows

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: memos-autodev[bot] <autodev-bot@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(plugin): paginate dirty-closed reward recovery (#2120)

* fix(plugin): page dirty-closed reward recovery

* fix(plugin): share dirty scan cursor type

---------

Co-authored-by: HarveyXiang <harvey_xiang@163.com>

* fix(adapter): fire session.close in daemon thread to unblock event loop (#1953)

* fix(adapter): fire session.close in daemon thread to unblock event loop

on_session_end() called bridge.request("session.close") inline with a
30 s blocking urlopen(). gateway/run.py calls this synchronously from
_handle_reset_command (an async fn), so the blocking I/O ran on the
asyncio event loop thread, preventing Discord heartbeats from firing and
causing forced reconnection after 10–30 s.

The session.close response is unused and errors are already suppressed,
so the call is semantically fire-and-forget. Moving it to a daemon thread
is the correct fix: the event loop is never blocked, the request still
goes out, and a 5 s timeout keeps it bounded if the bridge is dead.

Reproducer: Violet 2026-06-12 09:54 (agent.log lines 3629–3791).
Spec: ~/specs/memos-bridge-blocking-shutdown-spec.md

* Fix #1897: bug(memos-local-plugin): Hermes background model status checks can trigger paid  (#1899)

* Fix #1897: fix(memos-local-plugin): add LLM circuit breaker for terminal provider errors

Issue #1897 reported ~12,900 paid LLM requests in 24 h on Hermes
against a DeepSeek key with insufficient balance. The local
`system_model_status` row count (12,900) closely tracked the
provider-side `request_count` (11,344) for the same billing window.
The naming is misleading: `system_model_status` is not a health probe;
it is the audit row written once per LLM call (ok / fallback / error)
inside `core/llm/client.ts`. With no circuit breaker, every pipeline
subscriber (capture / session-relation / reward / L2 / L3 / skill /
retrieval LLM filter / world-model) kept firing on every turn / closed
episode / induction, generating one paid request each.

Add a per-`LlmClient` circuit breaker:

- Trips on terminal errors: HTTP 401/402/403 or messages containing
  `insufficient balance` / `invalid api key` / `unauthorized` /
  `account suspended` / `billing`.
- Open: short-circuits subsequent calls inside the facade without
  contacting the provider. Throws `MemosError(LLM_UNAVAILABLE)` with
  `details.circuitOpen=true` so existing catch blocks still work.
- Half-open after cool-down (default 5 min, configurable, min 30 s):
  next call probes the provider; success closes the breaker, terminal
  failure re-opens it for another cool-down.
- Host fallback rescues a call without tripping the breaker —
  fallback exists precisely to keep going when the primary is down.
- Coalesces `system_model_status="circuit_open"` audit rows to at
  most one per ~25 s while the breaker stays open, so we don't
  replace paid spam with audit-row spam.
- Exposes `circuitOpen` / `circuitOpenUntil` / `circuitOpenedReason`
  via `LlmClientStats` for the Overview viewer card.
- Enabled by default; legacy behaviour available via
  `circuitBreaker.enabled = false`.

Tests: 9 new vitest cases under `tests/unit/llm/client.test.ts`
covering trip on 402, trip on "insufficient balance" message, no
trip on generic transient, coalescing, half-open close on success,
host-fallback rescues without trip, disabled mode, stats fields,
and re-open on terminal probe failure. All 59 LLM and 28 pipeline
tests pass; `tsc --noEmit` clean.

Out of scope (tracked separately): 429 `Retry-After` handling
(issue #1620), per-tool rate limits, daily budget caps.

* Fix LLM breaker with host fallback

---------

Co-authored-by: autodev-bot <autodev@memtensor.local>
Co-authored-by: Jiang <33757498+hijzy@users.noreply.github.com>
Co-authored-by: GU TIANCHUN <96930846+TianchunGu@users.noreply.github.com>
Co-authored-by: Dubberman <48425266+whipser030@users.noreply.github.com>

---------

Co-authored-by: Memtensor-AI <project@memtensor.cn>
Co-authored-by: autodev-bot <autodev@memtensor.local>
Co-authored-by: Jiang <33757498+hijzy@users.noreply.github.com>
Co-authored-by: GU TIANCHUN <96930846+TianchunGu@users.noreply.github.com>
Co-authored-by: Dubberman <48425266+whipser030@users.noreply.github.com>

* feat: configurable log timezone (#1956)

feat(2c): add log timezone support

Co-authored-by: Fayenix <fayenix@users.noreply.github.com>
Co-authored-by: jiachengzhen <jiacz@memtensor.cn>

* feat: chunk batch reflection scoring (#2146)

Co-authored-by: Erick Wipprecht <53452309+chiefmojo@users.noreply.github.com>

* Fix #1897 recovery replay request storm (#1939)

* Fix #1897: fix(memos-local-plugin): add LLM circuit breaker for terminal provider errors

Issue #1897 reported ~12,900 paid LLM requests in 24 h on Hermes
against a DeepSeek key with insufficient balance. The local
`system_model_status` row count (12,900) closely tracked the
provider-side `request_count` (11,344) for the same billing window.
The naming is misleading: `system_model_status` is not a health probe;
it is the audit row written once per LLM call (ok / fallback / error)
inside `core/llm/client.ts`. With no circuit breaker, every pipeline
subscriber (capture / session-relation / reward / L2 / L3 / skill /
retrieval LLM filter / world-model) kept firing on every turn / closed
episode / induction, generating one paid request each.

Add a per-`LlmClient` circuit breaker:

- Trips on terminal errors: HTTP 401/402/403 or messages containing
  `insufficient balance` / `invalid api key` / `unauthorized` /
  `account suspended` / `billing`.
- Open: short-circuits subsequent calls inside the facade without
  contacting the provider. Throws `MemosError(LLM_UNAVAILABLE)` with
  `details.circuitOpen=true` so existing catch blocks still work.
- Half-open after cool-down (default 5 min, configurable, min 30 s):
  next call probes the provider; success closes the breaker, terminal
  failure re-opens it for another cool-down.
- Host fallback rescues a call without tripping the breaker —
  fallback exists precisely to keep going when the primary is down.
- Coalesces `system_model_status="circuit_open"` audit rows to at
  most one per ~25 s while the breaker stays open, so we don't
  replace paid spam with audit-row spam.
- Exposes `circuitOpen` / `circuitOpenUntil` / `circuitOpenedReason`
  via `LlmClientStats` for the Overview viewer card.
- Enabled by default; legacy behaviour available via
  `circuitBreaker.enabled = false`.

Tests: 9 new vitest cases under `tests/unit/llm/client.test.ts`
covering trip on 402, trip on "insufficient balance" message, no
trip on generic transient, coalescing, half-open close on success,
host-fallback rescues without trip, disabled mode, stats fields,
and re-open on terminal probe failure. All 59 LLM and 28 pipeline
tests pass; `tsc --noEmit` clean.

Out of scope (tracked separately): 429 `Retry-After` handling
(issue #1620), per-tool rate limits, daily budget caps.

* Fix LLM breaker with host fallback

* fix(plugin): bound recovery reflect replay LLM calls

---------

Co-authored-by: autodev-bot <autodev@memtensor.local>
Co-authored-by: Jiang <33757498+hijzy@users.noreply.github.com>
Co-authored-by: jiachengzhen <jiacz@memtensor.cn>

* fix: guard against episode storm stalling foreground sessions (#1844)

fix: guard against episode storm stalling foreground sessions (#1755)

Co-authored-by: jiachengzhen <jiacz@memtensor.cn>

* fix(logging): initialize logger from config in the standalone bridge (#1955)

The MemOS daemon (`bridge.cts`) resolved config inside
`bootstrapMemoryCoreFull` but never called `initLogger`, so the active
logger stayed on the `bootstrapConsoleOnly()` default with `tz` pinned to
"UTC". This made `logging.timezone` (PR #44) inert in the daemon — and the
rest of `logging.*` (level, channels, file/audit/llm/perf/events sinks)
dead too. Pretty/compact timestamps kept printing UTC regardless of config.

Add an opt-in `initLogging` flag to `BootstrapOptions`. When set,
`bootstrapMemoryCoreFull` calls `initLogger(config, home)` right after
config resolution, before anything logs. The standalone bridge passes it;
embedded plugin hosts (`adapters/openclaw/index.ts`) leave it off so the
host keeps control of its own logging.

Test: bootstrap with `logging.timezone` set + `initLogging: true` stamps
records with the configured tz; without the flag the logger is untouched.

* fix(bridge): add 20s timeout guard to core.shutdown() to prevent orphaned processes (#1799)

* fix(bridge): add shutdown timeout to prevent orphan bridge processes

core.shutdown() drains the L2/L3/skill flush pipeline which can block
on hanging LLM calls. Without a deadline the bridge never exits after
stdin EOF when the Python parent is already gone, re-creating the
process-leak condition. Race all three shutdown sites (daemon SIGTERM,
non-daemon SIGTERM, headless stdin-EOF) against a 20s timeout so the
process always terminates within a bounded time.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(bridge): wrap remaining core.shutdown() calls with 20s timeout

Three sites missed by the original patch (56ebe7a3) were calling
core.shutdown() without withShutdownTimeout, leaving the bridge process
able to hang indefinitely if L2/L3/skill LLM calls stalled at shutdown:

  • bridge.cts: EADDRINUSE exit (×2) — daemon can't bind viewer port
  • bridge.cts: viewer-running keepalive path — stdin closes but viewer
    is still serving; core.shutdown fires from the interval callback

All six core.shutdown() call sites now go through withShutdownTimeout,
guaranteeing the bridge exits within 20s regardless of which path is
taken. Adds bridge-shutdown-audit.md documenting the full call chain and
confirming no blocking sync calls prevent the timeout from firing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(docs): correct stale line references + move audit doc to apps/memos-local-plugin/docs/

- Removed companion-stable branch reference from header
- Updated bridge.cts line numbers to match main (per shinetata review)
- Removed companion-stable commit hash 56ebe7a3
- Moved from repo root to apps/memos-local-plugin/docs/ per review request

---------

Co-authored-by: Fayenix <fayenix@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(build): rebuild bridge.cjs when any TypeScript source is newer (#1850)

* fix(migrations): remove duplicate ALTER TABLE stmts from 008 now in 001

All columns added by 008-feedback-experience-metadata were backported into
001-initial.sql; the ALTER TABLE statements now fail on fresh DBs. Keeps
only the idempotent CREATE INDEX lines.

Also fix scripts/replay.py bridge_client import path (adapters/ is under
apps/memos-local-plugin/, not the repo root) and add
scripts/export-state-sessions.py for exporting state.db sessions to JSON
files compatible with replay.py.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(daemon): rebuild when any .ts source is newer than dist/bridge.cjs

_rebuild_if_stale() only checked bridge.cts mtime, so changes to
core/**/*.ts (e.g. memory-core.ts) didn't trigger an auto-rebuild on
gateway restart. Walk all .ts files under plugin_root and compare the
newest mtime against the compiled binary instead.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: remove 500-row cap that truncated viewer count displays (#1954)

* fix: remove 500-row cap that truncated viewer count displays

clampLimit() in _helpers.ts capped all repo list() calls at 500,
silently truncating skill/policy/world-model counts everywhere they
were derived from array length rather than a COUNT(*) query.

- Raise clampLimit cap 500 → 100,000 so metrics() and other internal
  analytics can read full datasets (fixes Analytics tab)
- Rewrite /api/v1/overview to use countSkills/countPolicies/
  countWorldModels/countEpisodes instead of list+length, making
  Overview counts correct regardless of scale (fixes Overview tab)
- Align countSkills to use limit:100_000 consistent with other
  count methods

* feat: chunk batch reflection scoring (#1957)

* Fix #1540: fix: (#1824)

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>

* Fix #1888: [Bug] test_system_parser.py: SystemParser.__init__() got an 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>

* Fix #1525: [Bug] clean_json_response crashes with cryptic AttributeError 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>

* Fix #1901: share_cube_with_user passes swapped args to _validate_cube_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>

* feat(memos): chunk batch reflection scoring

* Fix #1897: bug(memos-local-plugin): Hermes background model status checks can trigger paid  (#1899)

* Fix #1897: fix(memos-local-plugin): add LLM circuit breaker for terminal provider errors

Issue #1897 reported ~12,900 paid LLM requests in 24 h on Hermes
against a DeepSeek key with insufficient balance. The local
`system_model_status` row count (12,900) closely tracked the
provider-side `request_count` (11,344) for the same billing window.
The naming is misleading: `system_model_status` is not a health probe;
it is the audit row written once per LLM call (ok / fallback / error)
inside `core/llm/client.ts`. With no circuit breaker, every pipeline
subscriber (capture / session-relation / reward / L2 / L3 / skill /
retrieval LLM filter / world-model) kept firing on every turn / closed
episode / induction, generating one paid request each.

Add a per-`LlmClient` circuit breaker:

- Trips on terminal errors: HTTP 401/402/403 or messages containing
  `insufficient balance` / `invalid api key` / `unauthorized` /
  `account suspended` / `billing`.
- Open: short-circuits subsequent calls inside the facade without
  contacting the provider. Throws `MemosError(LLM_UNAVAILABLE)` with
  `details.circuitOpen=true` so existing catch blocks still work.
- Half-open after cool-down (default 5 min, configurable, min 30 s):
  next call probes the provider; success closes the breaker, terminal
  failure re-opens it for another cool-down.
- Host fallback rescues a call without tripping the breaker —
  fallback exists precisely to keep going when the primary is down.
- Coalesces `system_model_status="circuit_open"` audit rows to at
  most one per ~25 s while the breaker stays open, so we don't
  replace paid spam with audit-row spam.
- Exposes `circuitOpen` / `circuitOpenUntil` / `circuitOpenedReason`
  via `LlmClientStats` for the Overview viewer card.
- Enabled by default; legacy behaviour available via
  `circuitBreaker.enabled = false`.

Tests: 9 new vitest cases under `tests/unit/llm/client.test.ts`
covering trip on 402, trip on "insufficient balance" message, no
trip on generic transient, coalescing, half-open close on success,
host-fallback rescues without trip, disabled mode, stats fields,
and re-open on terminal probe failure. All 59 LLM and 28 pipeline
tests pass; `tsc --noEmit` clean.

Out of scope (tracked separately): 429 `Retry-After` handling
(issue #1620), per-tool rate limits, daily budget caps.

* Fix LLM breaker with host fallback

---------

Co-authored-by: autodev-bot <autodev@memtensor.local>
Co-authored-by: Jiang <33757498+hijzy@users.noreply.github.com>
Co-authored-by: GU TIANCHUN <96930846+TianchunGu@users.noreply.github.com>
Co-authored-by: Dubberman <48425266+whipser030@users.noreply.github.com>

---------

Co-authored-by: Memtensor-AI <project@memtensor.cn>
Co-authored-by: MemOS AutoDev <autodev@memtensor.ai>
Co-authored-by: Matthew <heimixiaozhuang@zju.edu.cn>
Co-authored-by: MemOS AutoDev <autodev@memos.ai>
Co-authored-by: MemOS AutoDev Bot <autodev@memtensor.local>
Co-authored-by: Jiang <33757498+hijzy@users.noreply.github.com>
Co-authored-by: GU TIANCHUN <96930846+TianchunGu@users.noreply.github.com>
Co-authored-by: Dubberman <48425266+whipser030@users.noreply.github.com>

* Revert "feat: chunk batch reflection scoring (#1957)" (#2145)

This reverts commit 21a27ed.

Co-authored-by: jiachengzhen <jiacz@memtensor.cn>

* test: stub overview count methods

---------

Co-authored-by: jiachengzhen <jiacz@memtensor.cn>
Co-authored-by: Memtensor-AI <project@memtensor.cn>
Co-authored-by: MemOS AutoDev <autodev@memtensor.ai>
Co-authored-by: Matthew <heimixiaozhuang@zju.edu.cn>
Co-authored-by: MemOS AutoDev <autodev@memos.ai>
Co-authored-by: MemOS AutoDev Bot <autodev@memtensor.local>
Co-authored-by: Jiang <33757498+hijzy@users.noreply.github.com>
Co-authored-by: GU TIANCHUN <96930846+TianchunGu@users.noreply.github.com>
Co-authored-by: Dubberman <48425266+whipser030@users.noreply.github.com>
Co-authored-by: zhaxi <syzsunshine219@gmail.com>

* fix(memos-local-openclaw): declare contracts.tools in plugin manifest (#1757)

OpenClaw runtime requires every plugin to declare contracts.tools
before registering agent tools. Without it, the gateway logs the
following warning on every registration:

    [plugins] plugin must declare contracts.tools before registering
    agent tools (plugin=memos-local-openclaw-plugin, source=index.js)

In a production OpenClaw deployment with active memos plugin, this
warning flooded gateway.err.log at ~1300 lines/day, masking real
errors and bloating logs by ~33MB/day.

This commit declares all 20 tools the plugin actually registers
(grep'd via `api.registerTool(...)` calls in index.ts):

  - memory_*: get, search, share, timeline, unshare, viewer, write_public
  - network_*: memory_detail, skill_pull, team_info
  - skill_*: file_get, files, get, install, publish, search, unpublish
  - task_*: share, summary, unshare

Verified: post-deploy gateway.err.log warning count = 0.

Refs: contracts.tools field per OpenClaw plugin spec (see
openclaw-lark and similar plugins as reference).

Co-authored-by: Richard-JC-Yan <150349207+Richard-JC-Yan@users.noreply.github.com>
Co-authored-by: jiachengzhen <jiacz@memtensor.cn>

* Fix #2148: [Bug] captureRunner uses reflectLlm (skill-evolver) for batch reflection — shoul (#2151)

* fix(plugin): wire capture reflection to main llm (#2148)

Capture batch reflection is a JSON-output task. When operators configure
skillEvolver.enableThinking=true alongside llm.enableThinking=false
(the typical Qwen3 setup), passing `deps.reflectLlm` into the capture
runner made the batch reflection run on the thinking-enabled skill-evolver
model, which emits <think> blocks that break JSON parsing and produce
`malformed JSON` errors during capture summarisation. Route the capture
runner's reflectLlm slot to `deps.llm` so JSON tasks stay on the
non-thinking main model, matching the l3Llm pattern from #1959.

- deps.ts: captureRunner.reflectLlm = deps.llm
- tests: regression guard in tests/unit/pipeline/capture-reflect-llm-wiring.test.ts

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

* docs(plugin): clarify reflectLlm exposure comment (#2148 OCR follow-up)

Enumerate the two read-only consumers that still reach `deps.reflectLlm`
(reward-runner evaluator metadata + Overview health card via
`resolveSkillEvolver`) and note that no other subscriber is wired to
it. Prevents future readers from misreading the previous prose as
"skill / capture also use reflectLlm".

---------

Co-authored-by: autodev-bot <autodev@memtensor.local>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: MemTensor Bot <bot@memtensor.local>
Co-authored-by: jiachengzhen <jiacz@memtensor.cn>

* test(logging): verify standalone bootstrap writes memos.log (#2147) (#2150)

* fix(local-plugin): call initLogger in bootstrapMemoryCoreFull

`bootstrapMemoryCoreFull` in `apps/memos-local-plugin/core/pipeline/memory-core.ts`
never invoked `initLogger(config, home)` after `loadConfig(home)`. The logger
stayed in the `bootstrapConsoleOnly()` fallback (console + memory buffer +
SSE broadcast only), so the `FileRotatingTransport` / `JsonlEventsTransport`
sinks were never wired and `MEMOS_HOME/logs/` (memos.log, error.log,
audit.log, llm.jsonl, perf.jsonl, events.jsonl) was left empty even though
`config.logging.file.enabled` defaults to true.

The fix adds `initLogger` to the logger import and calls it after the config
is resolved and before the first `rootLogger.child(...)` call. `initLogger`
is idempotent and swaps the active root in place, so existing child handles
keep working.

A new regression test `tests/unit/pipeline/bootstrap-init-logger.test.ts`
boots the pipeline against a tmp home, emits a marker line, and asserts
that `memos.log` is created on disk and contains the marker. It fails on
the previous code (ENOENT for memos.log) and passes with this fix.

Fixes #2147

* style(hermes): format daemon manager

---------

Co-authored-by: MemOS AutoDev <autodev@memtensor.local>
Co-authored-by: jiachengzhen <jiacz@memtensor.cn>

* fix: align generated skill guidance (#2100)

* feat(api): align OpenMem v1 SDK with cloud API

* fix: align generated skill guidance

* feat(api): align OpenMem v1 SDK with cloud API

* fix(memos-local): include json hint in user messages (#1756)

Co-authored-by: shinetata <149466187+shinetata@users.noreply.github.com>

---------

Co-authored-by: dingliang <2650876010@qq.com>
Co-authored-by: Qi Weng <bittergreen.wengqi@hotmail.com>
Co-authored-by: de1ty <7804799+de1tydev@users.noreply.github.com>
Co-authored-by: shinetata <149466187+shinetata@users.noreply.github.com>
Co-authored-by: jiachengzhen <jiacz@memtensor.cn>

* fix: Add dedicated document LLM, improve Markdown detection and context-aware structured extraction

---------

Co-authored-by: Memtensor-AI <project@memtensor.cn>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: bittergreen <bittergreen.wengqi@hotmail.com>
Co-authored-by: dingliang <2650876010@qq.com>
Co-authored-by: Erick Wipprecht <53452309+chiefmojo@users.noreply.github.com>
Co-authored-by: jiachengzhen <jiacz@memtensor.cn>
Co-authored-by: shinetata <149466187+shinetata@users.noreply.github.com>
Co-authored-by: Wenqiang Wei <46308778+endxxxx@users.noreply.github.com>
Co-authored-by: MemOS AutoDev <autodev@memtensor.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: memos-autodev[bot] <autodev-bot@users.noreply.github.com>
Co-authored-by: HarveyXiang <harvey_xiang@163.com>
Co-authored-by: autodev-bot <autodev@memtensor.local>
Co-authored-by: Jiang <33757498+hijzy@users.noreply.github.com>
Co-authored-by: GU TIANCHUN <96930846+TianchunGu@users.noreply.github.com>
Co-authored-by: Dubberman <48425266+whipser030@users.noreply.github.com>
Co-authored-by: Fayenix <fayenix@users.noreply.github.com>
Co-authored-by: de1ty <7804799+de1tydev@users.noreply.github.com>
Co-authored-by: MemOS AutoDev <autodev@memtensor.ai>
Co-authored-by: Matthew <heimixiaozhuang@zju.edu.cn>
Co-authored-by: MemOS AutoDev <autodev@memos.ai>
Co-authored-by: Richard-JC-Yan <128337844+Richard-JC-Yan@users.noreply.github.com>
Co-authored-by: Richard-JC-Yan <150349207+Richard-JC-Yan@users.noreply.github.com>
Co-authored-by: MemTensor Bot <bot@memtensor.local>
Co-authored-by: Ziyang Guo <121015044+RerankerGuo@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:plugin OpenClaw & Hermes status:in-progress Someone or AI is working on it | 人工或 AI 正在处理

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants