Skip to content

CR-2 guard fires on every L1 write in OpenClaw standalone mode (auto-wire missing) #208

Description

@ferminquant

Bug: CR-2 guard fires on every L1 write in OpenClaw standalone mode

What I see

In the OpenClaw host-adapter path (the in-process plugin install, not the standalone HTTP gateway), every L1 extraction emits this warning 4 times per session (once per memory written):

[memory-tdai][l1-writer] [CR-2 guard] writeMemory called without storage adapter; falling back to local fs at <dataDir>/records/2026-06-13.jsonl. In service mode this means JSONL is written to ephemeral pod fs and will be lost on restart. Caller must pass 'storage' to writeMemory.

It is reproducible on a fresh single-host install of memory-tencentdb v1.0.0 in OpenClaw. The warnings appear on every successful L1 batch, not on failures.

What I think is happening

The CR-2 guard at src/core/record/l1-writer.ts:202-217 was added in the 2026-05-19 fix to make a silent data-loss bug visible. The guard's own comment says standalone mode is supposed to be benign because "the gateway auto-wires a LocalStorageBackend at startup (server.ts:199-203)".

When I trace the OpenClaw path, I see the auto-wire is missing:

  1. src/gateway/server.ts:261 constructs new TdaiCore({ hostAdapter, config, sessionFilter }) — no storage.
  2. The OpenClaw plugin entry at the bundled dist/index.mjs:21372 (the OpenClawHostAdapter host) does the same — new TdaiCore({ hostAdapter, config: cfg, sessionFilter }).
  3. TdaiCore.constructor at src/core/tdai-core.ts:139 sets this.storage = opts.storage and never falls back, so this.storage is undefined.
  4. The L1/L2/L3 runners are constructed with storage: this.storage (src/core/tdai-core.ts:525, 541, 556) and forward undefined to extractL1Memories, which forwards it to writeMemory.
  5. writeMemory enters the else branch and emits the CR-2 warning.

So the warning is correct, but the auto-wire it depends on is not happening for the OpenClaw host-adapter entry — the comment in l1-writer.ts documents an intent that is not in the current code.

The standalone HTTP gateway at server.ts:261 has the same shape, but the same server.ts file does construct a LocalStorageBackend at lines 362, 558, and 1443, just not at the constructor at 261. So the auto-wire is half-implemented.

Impact

  • Standalone mode (single host): noisy logs, but data still lands at <dataDir>/records/<date>.jsonl because of the else branch's fs fallback. No actual data loss today, but the warning is misleading — it warns about a data-loss risk that is not real in this configuration.
  • Service mode (pod with a CosStorageBackend): the caller is expected to pass storage to TdaiCore. If they forget, the warning fires and data goes to ephemeral pod fs and is lost on restart. This is the real data-loss path the CR-2 fix was meant to catch, and it is still real.

The current code conflates the two cases in its warning text, which makes the warning un-actionable for the standalone operator.

Expected

The CR-2 guard should not fire in standalone mode. Either:

  1. Restore the auto-wire the guard comment promises: in TdaiCore.constructor, default this.storage to new LocalStorageBackend({ rootDir: this.dataDir, logger: this.logger }) when opts.storage is undefined. Service-mode callers that pass a CosStorageBackend still win because the fallback only fires when undefined. This is a one-line change in the constructor and matches the documented intent.
  2. Or, change the warning text so it no longer claims the auto-wire is happening when it is not. Lower-quality fix, since the CR-2 root cause is still latent for service-mode callers who forget to pass storage.

I prefer (1). It is closer to the original design intent, makes the guard actually fire only for the real bug (service mode with missing storage), and removes the noisy logs in standalone mode.

Reproduction

Fresh OpenClaw install, memory-tencentdb v1.0.0 (npm @tencentdb-agent-memory/memory-tencentdb latest). Open a session, send a few turns, wait for an L1 extraction round. Warnings appear in journalctl --user -u openclaw-gateway.service. The L1 records themselves do land at ~/.openclaw/memory-tdai/records/<date>.jsonl, so the data is safe; only the warning text is wrong.

Proposed fix

In src/core/tdai-core.ts:

import { LocalStorageBackend } from "./storage/local-backend.js";
// ...
constructor(opts: TdaiCoreOptions) {
  // ...
  // CR-2 (2026-05-19): restore the auto-wire for standalone mode that the
  // l1-writer guard comment refers to. The guard expects every writeMemory
  // call to be backed by a StorageAdapter. Service-mode callers pass one
  // explicitly (e.g. CosStorageBackend). Standalone installs omit it and
  // rely on the gateway to fall back to local fs. We restore that
  // auto-wire here. Custom-storage callers still win because the fallback
  // only fires when opts.storage is undefined.
  this.storage = opts.storage ?? new LocalStorageBackend({ rootDir: this.dataDir, logger: this.logger });
}

I am happy to send a PR with this change if maintainers agree. I have already tested it locally on v1.0.0 / OpenClaw — CR-2 warnings stop, L1 writes still land at the same JSONL path (the LocalStorageBackend wraps the same fs append with O_APPEND, so the on-disk format is identical).

Environment

  • memory-tencentdb v1.0.0 (npm)
  • OpenClaw v2026.5.20
  • Node 22.22.0
  • Host: WSL2 Ubuntu, single-host install, OpenClaw host-adapter path

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions