openclaw - ✅(Solved) Fix Proposal: add memory_add for canonical semantic memory writes [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#70049Fetched 2026-04-23 07:29:58
View on GitHub
Comments
0
Participants
1
Timeline
0
Reactions
0
Author
Participants

Add memory_add to memory-core as the canonical way for agents to write durable memory.

The tool should replace ad-hoc model edits to memory markdown with a structured write path. It should still write to workspace markdown first, specifically memory/YYYY-MM-DD.md, so markdown remains the source of truth and backends consume/index it rather than owning a separate write source.

Each write should create one complete semantic memory unit with a persistent memoryId, category, timestamps, and an explicit markdown boundary. That gives backends enough metadata to preserve the memory as coherent content during indexing and recall, and gives future maintenance tools a stable target for replacing or deleting the memory later.

Error Message

"error": "text_required" The exact error codes can be refined in the PR, but the important contract is that successful writes return a stable memoryId, and duplicate detection is explicit and non-writing.

Root Cause

Add memory_add to memory-core as the canonical way for agents to write durable memory.

The tool should replace ad-hoc model edits to memory markdown with a structured write path. It should still write to workspace markdown first, specifically memory/YYYY-MM-DD.md, so markdown remains the source of truth and backends consume/index it rather than owning a separate write source.

Each write should create one complete semantic memory unit with a persistent memoryId, category, timestamps, and an explicit markdown boundary. That gives backends enough metadata to preserve the memory as coherent content during indexing and recall, and gives future maintenance tools a stable target for replacing or deleting the memory later.

PR fix notes

PR #70540: feat(memory-core): add memory_add tool

Description (problem / solution / changelog)

Context

Refs #70049

This PR adds the first minimal memory_add write primitive for durable semantic memories. It stores each write as a markdown-native memory block in memory/YYYY-MM-DD.md:

Remember this semantic memory.

----

---- is both a readable Markdown horizontal rule and OpenClaw's semantic block separator. User text lines that exactly equal ---- are written as ---, which still renders as a horizontal rule but does not create a false OpenClaw block boundary.

What This PR Implements

  • Adds memory_add in the memory-core plugin.
  • Accepts a single text field and validates empty input as text_required.
  • Uses the configured user timezone for the daily file name.
  • Appends one memory block to the daily memory markdown file.
  • Does not perform semantic duplicate detection in this first slice; duplicate management is left for future list/replace/remove flows.

Backend Behavior

  • Builtin: for daily memory files, indexing parses ---- as a hard semantic boundary. Chunks are built from one memory block at a time, so builtin chunks do not cross memory blocks. The separator line is excluded from snippets and embedding text.
  • QMD: consumes the same markdown files as source of truth. Because ---- renders as a normal horizontal rule, QMD can use it as a natural chunk-boundary signal without any OpenClaw-specific metadata text.
  • Index refresh continues to use the existing watcher/search/manual sync mechanisms.
  • Other markdown files such as MEMORY.md, memory.md, and non-daily memory files remain raw markdown.

Fallback Behavior

If a daily memory file is manually edited incorrectly, the fallback is still acceptable: malformed or separator-free content is indexed as normal markdown memory text rather than failing the index. The worst case is less precise chunking, not data loss or an unreadable memory file.

Validation

  • pnpm test extensions/memory-core/src/memory/memory-blocks.test.ts extensions/memory-core/src/tools.test.ts extensions/memory-core/src/memory/index.test.ts src/gateway/tools-invoke-http.test.ts src/agents/tool-catalog.test.ts src/agents/pi-tools.policy.test.ts
  • pnpm test:changed
  • pnpm check:changed passed conflict marker checks, core/core-test typecheck, extension/extension-test typecheck, core lint, and extension lint. It stops at app lint locally because swiftlint is not installed: sh: swiftlint: command not found.

Changed files

  • apps/shared/OpenClawKit/Sources/OpenClawKit/Resources/tool-display.json (modified, +7/-0)
  • docs/gateway/configuration-reference.md (modified, +1/-1)
  • docs/gateway/sandbox-vs-tool-policy-vs-elevated.md (modified, +1/-1)
  • docs/tools/index.md (modified, +1/-1)
  • extensions/memory-core/index.ts (modified, +10/-1)
  • extensions/memory-core/src/memory/add-memory-block.ts (added, +86/-0)
  • extensions/memory-core/src/memory/index.test.ts (modified, +42/-0)
  • extensions/memory-core/src/memory/manager-embedding-ops.ts (modified, +49/-2)
  • extensions/memory-core/src/memory/memory-blocks.test.ts (added, +38/-0)
  • extensions/memory-core/src/memory/memory-blocks.ts (added, +81/-0)
  • extensions/memory-core/src/tools.shared.ts (modified, +5/-1)
  • extensions/memory-core/src/tools.test-helpers.ts (modified, +15/-1)
  • extensions/memory-core/src/tools.test.ts (modified, +58/-1)
  • extensions/memory-core/src/tools.ts (modified, +44/-0)
  • src/agents/pi-tools.policy.test.ts (modified, +2/-1)
  • src/agents/tool-catalog.test.ts (modified, +1/-0)
  • src/agents/tool-catalog.ts (modified, +8/-0)
  • src/agents/tool-display-config.ts (modified, +5/-0)
  • src/gateway/tools-invoke-http.test.ts (modified, +30/-1)
  • src/gateway/tools-invoke-http.ts (modified, +4/-2)

Code Example

export const MemoryCategorySchema = Type.Union([
  Type.Literal("preference"),
  Type.Literal("fact"),
  Type.Literal("decision"),
  Type.Literal("entity"),
  Type.Literal("other"),
]);

export type MemoryCategory = Static<typeof MemoryCategorySchema>;

export const MemoryAddSchema = Type.Object({
  text: Type.String({ description: "Complete semantic memory unit to store" }),
  category: Type.Optional(MemoryCategorySchema),
});

type MemoryAddParams = Static<typeof MemoryAddSchema>;

---

type MemoryKind = "UNIT" | "RAW";

---

{
  "action": "created",
  "memoryId": "UNIT:<uuid>",
  "kind": "UNIT",
  "path": "memory/2026-04-22.md",
  "category": "decision",
  "text": "Use pnpm for dependency management in this workspace."
}

---

{
  "action": "duplicate",
  "existing": {
    "memoryId": "UNIT:<uuid>",
    "kind": "UNIT",
    "path": "memory/2026-04-22.md",
    "category": "decision",
    "text": "Use pnpm for dependency management in this workspace."
  }
}

---

{
  "action": "failed",
  "error": "text_required"
}

---

<!-- openclaw:memory:item:start memory_id=UNIT:<uuid> category=decision created_at=... updated_at=... -->
Use pnpm for dependency management in this workspace.
<!-- openclaw:memory:item:end -->
RAW_BUFFERClick to expand / collapse

Summary

Add memory_add to memory-core as the canonical way for agents to write durable memory.

The tool should replace ad-hoc model edits to memory markdown with a structured write path. It should still write to workspace markdown first, specifically memory/YYYY-MM-DD.md, so markdown remains the source of truth and backends consume/index it rather than owning a separate write source.

Each write should create one complete semantic memory unit with a persistent memoryId, category, timestamps, and an explicit markdown boundary. That gives backends enough metadata to preserve the memory as coherent content during indexing and recall, and gives future maintenance tools a stable target for replacing or deleting the memory later.

Motivation

memory-core currently has recall tools, but no explicit semantic write primitive. Agents can edit memory files directly or rely on backend-specific tools, which makes memory writes inconsistent across backends.

The missing abstraction is a semantic memory unit. When a user explicitly asks the agent to remember something, that memory is usually a complete preference, decision, fact, correction, or entity detail. It should be stored and indexed as one coherent unit, not accidentally split into unrelated chunks.

We have already implemented and used this pattern in production: an explicit memory-write tool stores user-approved memories as managed markdown units, assigns stable IDs, checks duplicates, and lets memory backends consume the markdown corpus. This proposal extracts the portable part of that design into memory-core so it can work across backends.

Relevant community feedback:

  • #43747: memory management feels inconsistent and chaotic.
  • #48558: asks for native memory-tool-style behavior.
  • #62184: needs a better path for explicit corrections and lessons.
  • #68751: shows the risk of raw transcript/session text being reinterpreted as memory.

Other related discussions point in the same direction: #43002, #59095, #50096, #68449, #68473, #67697, #6877, #6878, #34951, #8185, #45608, #56072, #24173, #60572, and #65411.

Proposal

Add a memory_add tool that:

  • Accepts memory text and optional category.
  • Normalizes and validates the text.
  • Checks existing memory blocks for duplicates.
  • Appends a managed memory unit to memory/YYYY-MM-DD.md.
  • Returns duplicate when similar memory already exists.
  • Returns created with a stable memoryId when a new unit is written.

Tool schema

export const MemoryCategorySchema = Type.Union([
  Type.Literal("preference"),
  Type.Literal("fact"),
  Type.Literal("decision"),
  Type.Literal("entity"),
  Type.Literal("other"),
]);

export type MemoryCategory = Static<typeof MemoryCategorySchema>;

export const MemoryAddSchema = Type.Object({
  text: Type.String({ description: "Complete semantic memory unit to store" }),
  category: Type.Optional(MemoryCategorySchema),
});

type MemoryAddParams = Static<typeof MemoryAddSchema>;

text is the complete semantic memory unit to store. category is optional metadata for indexing, filtering, and future UI display.

The category set follows the existing memory-lancedb/production convention. When category is not provided, memory_add can infer it using deterministic rules rather than requiring an LLM call. The initial heuristic can stay conservative, mapping explicit preferences, decisions, factual statements, entity details, and fallback cases into the fixed enum.

Memory kind

Introduce a small MemoryKind distinction:

type MemoryKind = "UNIT" | "RAW";

UNIT memories are managed semantic units created by tools such as memory_add. They have stable UNIT:<uuid> IDs and are safe targets for future mutation tools.

RAW memories are existing plain markdown content parsed from the memory corpus. They can be searched and used for duplicate detection, but should be treated as read-only unless later converted into managed units.

This gives memory-core a clear boundary between curated, user-approved memory units and legacy/raw markdown text.

Result shape

Created:

{
  "action": "created",
  "memoryId": "UNIT:<uuid>",
  "kind": "UNIT",
  "path": "memory/2026-04-22.md",
  "category": "decision",
  "text": "Use pnpm for dependency management in this workspace."
}

Duplicate:

{
  "action": "duplicate",
  "existing": {
    "memoryId": "UNIT:<uuid>",
    "kind": "UNIT",
    "path": "memory/2026-04-22.md",
    "category": "decision",
    "text": "Use pnpm for dependency management in this workspace."
  }
}

Failed:

{
  "action": "failed",
  "error": "text_required"
}

The exact error codes can be refined in the PR, but the important contract is that successful writes return a stable memoryId, and duplicate detection is explicit and non-writing.

Managed memory unit

Example markdown representation:

<!-- openclaw:memory:item:start memory_id=UNIT:<uuid> category=decision created_at=... updated_at=... -->
Use pnpm for dependency management in this workspace.
<!-- openclaw:memory:item:end -->

The HTML comments are storage metadata. They define the unit boundary, stable ID, category, and timestamps, but should be filtered out of indexed chunks and recall text. Backends should index the memory body as the semantic content, while preserving the memoryId as metadata.

Backends should preserve this unit boundary during indexing and recall. If they chunk internally, they should keep the shared memoryId so future mutation can operate on the complete memory.

Future work

Once memory_add establishes the managed memory unit format, memory-core can add explicit mutation tools on top of the same memoryId contract:

  • memory_replace or memory_update to replace the text of a UNIT:* memory while preserving its identity and creation metadata.
  • memory_remove or memory_delete to remove a UNIT:* memory by stable ID.
  • Query-assisted mutation flows can return candidates first, then require a memoryId for destructive changes.
  • RAW:* memories should remain searchable and useful for duplicate detection, but not directly mutable by these tools.

The naming can be decided separately, but the core requirement is that future mutation operates on complete semantic units, not arbitrary text spans.

Validation

Tests should cover:

  • Creating a managed unit in memory/YYYY-MM-DD.md.
  • Returning duplicate without writing.
  • Preserving semantic unit boundaries.
  • Filtering marker comments out of indexed chunks and recall text.
  • Distinguishing UNIT from RAW memory.
  • Treating raw markdown as searchable but not directly writable.
  • Working without backend-specific storage.

extent analysis

TL;DR

Implement the proposed memory_add tool in memory-core to provide a canonical way for agents to write durable memory, ensuring consistent and structured memory writes across backends.

Guidance

  1. Define the memory_add function: Implement the memory_add tool according to the proposed schema, accepting memory text and optional category, normalizing and validating the text, checking for duplicates, and appending a managed memory unit to memory/YYYY-MM-DD.md.
  2. Introduce MemoryKind distinction: Implement the MemoryKind type to distinguish between UNIT memories (managed semantic units) and RAW memories (existing plain markdown content).
  3. Implement result shapes: Define the result shapes for created, duplicate, and failed memory additions, including the stable memoryId, kind, path, category, and text.
  4. Preserve semantic unit boundaries: Ensure that backends preserve the unit boundary during indexing and recall, keeping the shared memoryId for future mutation.

Example

const memoryAdd = (text: string, category?: MemoryCategory): MemoryAddResult => {
  // Normalize and validate the text
  const normalizedText = normalizeText(text);
  
  // Check for duplicates
  const existingMemory = findDuplicateMemory(normalizedText);
  if (existingMemory) {
    return { action: 'duplicate', existing: existingMemory };
  }
  
  // Create a new managed memory unit
  const memoryId = generateMemoryId();
  const memoryUnit = createMemoryUnit(normalizedText, category, memoryId);
  
  // Append the memory unit to the markdown file
  appendMemoryUnitToMarkdown(memoryUnit);
  
  return { action: 'created', memoryId, kind: 'UNIT', path: 'memory/YYYY-MM-DD.md', category, text: normalizedText };
};

Notes

The implementation should consider the proposed schema, result shapes, and MemoryKind distinction to ensure consistent and structured memory writes. Additional tests should be written to cover the validation requirements.

Recommendation

Apply the proposed memory_add tool implementation to provide a canonical way for agents to write durable memory, ensuring consistent and structured memory writes across backends. This will help to address the inconsistencies and chaos in memory management reported in the community feedback.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING