Skip to main content
This page covers connecting OpenClaw to a Datris MCP server so the agent can use Datris as its long-term semantic memory — local memory files stay canonical, while Datris becomes the retrieval/index layer. For server-side details (architecture, transports, available tools), see MCP Server (AI Agent Integration).

Why route OpenClaw memory through Datris

This isn’t a “no memory vs. memory” comparison. OpenClaw already has memory. It’s a comparison about enforcement, scale, and reuse. The short version:
  1. OpenClaw’s built-in memory is best-effort — the model decides when to save and when to look — so long sessions, restarts, and context compaction quietly erode recall. Datris moves memory out of the prompt and enforces it at the system layer, so your agent stops forgetting.
  2. The same MCP connection that gives OpenClaw enforced semantic memory also exposes 30+ Datris tools — pipelines, ingestion, SQL, vector search — so you’re not bolting on a memory product, you’re handing your agent a full data platform.

vs. OpenClaw’s default memory (best-effort, not enforced)

OpenClaw ships with persistent memory out of the box — daily memory files, a curated MEMORY.md, and semantic search over archived sessions. It works. The catch is that everything is advisory: the model decides when to save, what to save, and whether to search before answering. Long sessions, context compaction, and restarts all chip away at recall. You’ll see it fail the same way every time — a fact you established in week one stops surfacing in week three, not because it’s gone, but because the model didn’t think to look for it. Datris-backed memory enforces capture and retrieval at the system layer instead of leaving it to the prompt. Memory is written outside the agent’s session, and relevant context is reintroduced on every turn. Restarts don’t matter. Long conversations don’t matter. The agent reasons with the same facts every time.

vs. flat file-based memory (raw markdown, JSON blobs)

Hand-rolled file memory — a PREFERENCES.md here, a CONTEXT.md there, a notes/ folder with grep on top — is the path most people start down. It works for a week. Past a few dozen entries the agent ends up either loading whole files into context or substring-matching for keywords that may not appear in the original note. A question phrased as “what did we decide about the pipeline architecture?” misses notes filed under “ingestion topology” or “data flow design.” Datris stores memories in pgvector (or Qdrant, Weaviate, Milvus, Chroma — your call) and retrieves by meaning, not exact wording. The same question finds the right note regardless of how it was originally phrased.

vs. MemGPT/Letta-class agent runtimes

MemGPT (now Letta) does semantic memory well — that’s the system that popularized the LLM-as-OS framing for agent memory. The trade-off is that Letta is a runtime, not a memory layer. Adopting it means running your agent inside their framework: their agent loop, their tool execution, their state model. That’s a fine choice from a clean slate. It’s a heavy migration if you already have an agent you like — including OpenClaw. Datris is a memory layer exposed over MCP. OpenClaw stays OpenClaw. Your agent loop, your tools, your skills — all unchanged. Point your MCP client at Datris and the agent gains enforced, semantic, persistent memory without rewriting anything.

What you actually get

Indexing local memory files into Datris gives you the best of both worlds — markdown stays human-editable and version-controllable, while Datris handles retrieval at scale.
  • Agent-runtime-agnostic. Datris exposes memory through MCP, so OpenClaw, Claude Desktop, Cursor, or any custom MCP client can point at the same instance and share the same memory. No bespoke integration per agent, no rewriting your agent loop to match someone else’s runtime.
  • Tool-rich, not just retrieval. The same MCP connection that gives OpenClaw memory also exposes pipeline management, ingestion, and SQL and vector query tools — over 30 in total. Memory-only products stop at recall; Datris hands the agent the rest of a data platform on the same connection.
  • Auditable. The Agents tab in the Datris UI shows OpenClaw’s memory tool calls as they happen, so you can see exactly what the agent searched for, what came back, and what it stored.
  • Semantic recall, not substring search. Ask in natural language and get the right snippet back even when the wording doesn’t match — “what did we decide about the pipeline architecture?” finds the note filed under “ingestion topology.”
  • Scales past the context window. Large memory corpora that would never fit into a single prompt become retrievable on demand. The agent pulls only what’s relevant for the current turn.
  • Files remain the source of truth. For memory you author yourself, the markdown files are yours — Datris is a derived index. Edit locally, re-ingest when ready. No vendor lock-in on your notes.

Step 1 — Set the embedding model to OpenAI text-embedding-3-small

Before ingesting any memory, pick the embedding model the pipeline will use to encode it. For long-term memory we recommend OpenAI with text-embedding-3-small — it’s the best balance of recall quality, latency, and cost for this use case in our testing. In the Datris UI:
  1. Open the Configuration tab, then the AI Providers sub-tab.
  2. In the Embedding Provider section, choose OpenAI as the provider and text-embedding-3-small as the model.
  3. Enter your OpenAI API key in the right-hand API Keys panel (if you haven’t already).
  4. Click Save Configuration. Changes take effect immediately — no restart required.
Set the embedder before you create the memory pipeline. Vector dimensions are pinned at pipeline-creation time — bge-m3 (the bundled default) is 1024-dim, text-embedding-3-small is 1536-dim. Switching embedders after ingestion will fail-fast with a dimension-mismatch error and you’ll have to drop the destination collection and re-ingest. See AI Configuration for the full provider matrix.

Step 2 — Install the Datris CLI

The memory skill prefers MCP tools but falls back to the datris CLI for health checks, stalled-job recovery, and operating when the MCP server is unreachable. It also declares datris as a required binary in its install metadata, so it has to be on your PATH before the skill will activate cleanly. Install via Homebrew:
brew tap datris/tap
brew install datris
Verify:
datris health
You should see every backend service report healthy. See docs.datris.ai/cli for the full command reference.

Step 3 — Register the Datris MCP server with OpenClaw

Point OpenClaw at the local Datris MCP server (the SSE endpoint exposed by the standard Docker stack on port 3000):
openclaw mcp set datris '{"url":"http://localhost:3000/sse"}'
Verify the registration:
openclaw mcp show datris
You should see the datris server listed with its URL. SSE registration also means OpenClaw will appear in the Datris UI’s Agents tab with live tool-call streaming — useful for watching what the agent is actually doing during ingestion and retrieval.

Step 4 — Install the Datris memory skill

A one-shot prompt isn’t enough — memory work is ongoing (bootstrap, incremental sync, retrieval defaults, handling renames and deletes), and the agent needs persistent operating instructions, not just a single bootstrap run. OpenClaw uses skills — markdown files under .openclaw/workspace/skills/ — for exactly this. Install the Datris memory skill once and OpenClaw auto-triggers it whenever the user mentions saving, recalling, ingesting, syncing, or searching memory (and even on memory-shaped questions like “what did I note about X” that don’t say “Datris” out loud). You have two options for installing the skill.
openclaw skills install datris-memory
This pulls the latest published version of the skill, drops it at ~/.openclaw/workspace/skills/datris-memory/skill.md, and you’re done. Skip ahead to Step 5.

Option B: install manually

Create the skill directory:
mkdir -p ~/.openclaw/workspace/skills/datris-memory
Then create ~/.openclaw/workspace/skills/datris-memory/skill.md with the contents below:
---
name: datris-memory
description: Long-term semantic memory layer for AI agents, built on the Datris Platform via MCP. Local markdown files (MEMORY.md, memory/*.md) are the source of truth; Datris is the queryable index, rebuilt from them with strict one-file-per-upload provenance and incremental mtime-based sync (background timer, post-write flush, on-demand). Triggers on memory-shaped questions — saving, recalling, ingesting, syncing, searching — even when the user doesn't say "Datris."
version: 1.0.0
metadata:
  openclaw:
    requires:
      bins:
        - datris
        - docker
    envVars:
      - name: MCP_SERVER_URL
        required: false
        description: Datris MCP server SSE endpoint. Defaults to http://localhost:3000/sse.
    install:
      - id: brew
        kind: brew
        formula: datris/tap/datris
        bins: [datris]
        label: Install Datris CLI (brew)
---

# Datris memory layer

Datris is the long-term semantic memory layer. Local memory files are the source of truth. Datris is rebuilt from them — never the other way around.

## Prerequisites

This skill assumes a local Datris install:

- **Datris Platform** (includes the MCP server) — Docker-based, see [docs.datris.ai/installation](https://docs.datris.ai/installation). The MCP server runs at `http://localhost:3000/sse` by default; override with `MCP_SERVER_URL`.
- **Datris CLI**`brew tap datris/tap && brew install datris`. See [docs.datris.ai/cli](https://docs.datris.ai/cli) for command reference.

Confirm both are working before relying on this skill: `datris health` pings every backend service the memory pipeline needs.

## Rules

- Prefer Datris MCP tools over the CLI for memory operations. The CLI is a fallback for the cases listed under "When to use the CLI" below.
- **One file in = one `upload_data` call.** Never concatenate, bundle, or consolidate multiple memory files into a single corpus upload. Each file's name is its provenance — it must round-trip cleanly into Datris and back out in retrieval results. Bundling looks like an optimization and is not one: it permanently breaks provenance, blocks per-file incremental sync, and makes resets harder.
- Upload each file as-is. Let Datris chunk server-side. Do not pre-chunk. Do not use a document tap for local files. The only time to split a single file is when that one file genuinely exceeds the upload limit; in that case, split it with explicit provenance markers in the chunk filenames (e.g. `MEMORY.md.part1`, `MEMORY.md.part2`).
- Upload files in parallel. Each `upload_data` call returns a job token immediately — fire all uploads first, then poll. Do not wait for one job to finish before starting the next.
- Poll job status to completion before claiming any ingestion succeeded. Polling is for verification, not for gating subsequent uploads.
- Verify retrieval with a semantic search after every ingestion run.
- Memory pipelines must target a vector destination (pgvector, Qdrant, Weaviate, Milvus, or Chroma).
- The embedding model is pinned at pipeline-creation time. Vector dimensions cannot change after the fact. Confirm the embedder matches the pipeline before ingesting. Switching embedders means dropping and recreating the destination collection.
- **Place every created resource in the `openclaw` data catalog by default.** Pipelines, taps, secrets, destination collections — anything the agent creates in Datris on OpenClaw's behalf goes in the catalog named `openclaw` unless the user explicitly directs otherwise. This keeps OpenClaw's footprint cleanly separated from other Datris workloads on the same instance, makes cleanup and auditing trivial, and lets multiple OpenClaw users share an instance without colliding. If an existing resource the agent wants to reuse lives in a different catalog, do not migrate it silently — surface the mismatch to the user and ask before continuing. Set the catalog at creation time (`create_pipeline` accepts `catalog: "openclaw"`; `create_tap` writes the field through) or after the fact via the `set_catalog` MCP tool.

## When to use the CLI

MCP tools are the default. Reach for the `datris` CLI in these specific situations:

- **Health checks during bootstrap or troubleshooting.** `datris health` confirms every backend service is up. Use it as a more thorough cross-check when MCP `check_service_health` returns ambiguous results, or when diagnosing a stuck ingestion.
- **MCP server unavailable.** If the MCP connection is down, `datris ingest <file> --dest pgvector` and `datris search "<query>" --store pgvector --collection <name>` are the equivalent fallbacks for ingestion and retrieval. Use these only to keep the user unblocked; restore MCP-based operation as soon as the server is reachable.
- **Pipeline status when MCP polling stalls.** `datris status <pipeline>` is a clean way to read the latest job state if the MCP `get_job_status` loop has lost track of which token to follow.
- **Spot-checks the user runs themselves.** When the user wants to verify an ingestion by hand, point them at `datris search` against the destination collection rather than walking them through MCP tool calls.

Log every CLI invocation in the audit log (`memory/<today>.md`) the same way an MCP call would be logged — same provenance discipline applies.

## First-run bootstrap

1. Read the Datris MCP resources and tool descriptions. Understand the pipeline, upload, job-status, and search workflows before acting.
2. Check service health via the MCP `check_service_health` tool. If it returns ambiguous or partial results, fall back to `datris health` for a more detailed per-service view.
3. Reuse an existing vector pipeline for memory in the `openclaw` catalog if one exists. Otherwise create one in the `openclaw` catalog — pgvector is fine. If a memory pipeline exists outside the `openclaw` catalog, surface that to the user before reusing or migrating it.
4. Ingest `MEMORY.md` and each `memory/*.md` file via its own `upload_data` call — one upload per file, no exceptions. Do not concatenate them into a single corpus document, even if the total set is small. Fire the uploads in parallel and collect all the job tokens before polling.
5. Poll all jobs concurrently until every one is done. Call `get_job_status(pipeline_token=...)` per job and treat it as complete when `rollup.allDone` is `true`; the per-job outcome is `rollup.status` (`success` / `warning` / `error`), with failure detail in `rollup.jobs[].lastError`.
6. Verify with two or three representative semantic queries. Confirm both that (a) the expected content comes back, and (b) results show real source filenames (`MEMORY.md`, `memory/2026-05-06.md`, etc.) — not a consolidated corpus filename or any other synthetic name. The filename round-trip check is the early-warning signal that the one-file-per-upload rule is being followed; if results show a corpus filename, stop and apply the remediation workflow below.
7. Record the run in `memory/<today>.md`: pipeline used, files ingested, verification queries and results, any failures.
8. Propose an incremental sync strategy for future edits.

## Ongoing sync

Memory files change continuously — the agent writes to them during sessions, the user edits them in their editor between sessions, and new dated files appear over time. Sync is incremental and runs lazily, in three modes:

### When to sync

1. **Periodic background sync.** A timer-driven sweep runs every 30 minutes by default — diff memory files by `mtime` against the last sync record, upload anything stale. This runs out-of-band: it never blocks an agent response or a user query. Cadence is configurable: faster (5–10 min) for users actively editing memory between sessions, slower (hourly or more) for read-mostly use. The right value is whatever keeps the staleness window short enough that the user rarely needs to force a sync.

2. **End of any agent response that wrote to memory.** When the agent edits or creates a memory file during a turn, flush those uploads before the response is considered complete — including polling to completion. This puts the cost on the response that did the writing, not on later queries, and guarantees that a follow-up question in the same conversation can retrieve what the agent just wrote. Never let an agent-authored memory write wait for the next timer tick.

3. **On explicit user request.** Phrases like "sync memory," "save what we discussed," "update Datris with my recent notes" — full diff sweep across all memory files, immediate sync. This is the user's escape valve for the case where they just edited a file in their editor and want it queryable right now rather than waiting for the next timer tick.

### Staleness window — and why it's acceptable

Memory edits made outside of an agent session — for example, the user editing `MEMORY.md` in their editor between conversations — may be up to one timer interval behind in retrieval results. That is the explicit trade. Query latency stays predictable, ingestion is invisible, and the user has a one-line escape hatch ("sync memory") when freshness matters. Do not try to close the staleness window by syncing on every retrieval; that path puts ingestion cost on the user's wait time and is the design this skill replaces.

### Detecting what changed

Compare each memory file's filesystem `mtime` against the most recent sync timestamp recorded for that file in the `memory/<date>.md` audit logs. Three cases:

- **No sync record exists** — treat as a new file, ingest it.
- **`mtime` newer than last-sync timestamp** — re-upload.
- **`mtime` unchanged** — skip. Do not re-ingest unchanged files; the point of incremental sync is to do less work.

A content hash is more reliable than `mtime` if the user touches files without editing them (some editors do this on save), but `mtime` is sufficient as a default. Switch to hashing only if redundant uploads start showing up in the audit log.

### Sync workflow

Classify each changed file into one of four cases and act accordingly:

- **Edited file** — re-upload via `upload_data`. Pipelines upsert on source filename, so this overwrites the file's existing chunks cleanly.
- **New file** (no prior sync record) — upload via `upload_data` like any other.
- **Renamed file** — treat as `delete + add`, never as `update`. Delete chunks for the old filename from the destination collection, then upload the new file. Otherwise the index keeps orphan chunks under the old name.
- **Deleted file** — delete its chunks from the destination collection. Do not leave orphans.

Then:

- Fire all uploads in parallel, collect the job tokens, poll concurrently to completion.
- Verify with one or two semantic queries that touch the changed content. Confirm filenames in results reflect the post-sync state — no stale entries from before a rename or delete, no consolidated-corpus names.
- Append a per-file entry to today's audit log: filename, change type (edit / new / rename / delete), timestamp, verification result.

## Inheriting a consolidated-corpus pipeline

If the existing memory pipeline was bootstrapped with a single consolidated upload (a `*-corpus-*.md` source file, or any upload whose filename doesn't match a real memory file), the pipeline has broken provenance and cannot be incrementally synced cleanly. Do not patch around it. Reset and re-ingest:

1. Confirm with the user before resetting — destination collections may contain manual edits.
2. Drop the destination collection (or recreate the pipeline with the same name).
3. Re-ingest each canonical memory file individually per the bootstrap workflow above.
4. Verify with semantic queries that retrieval results now show real source filenames (`MEMORY.md`, `memory/2026-05-06.md`, etc.) rather than a corpus filename.

## Retrieval

When the user asks a memory-shaped question, reach for `vector_search` (or `ai_answer` for synthesis) against the memory pipeline before grepping local files. Substring search on local markdown is a fallback, not the default. If the MCP layer is unreachable, `datris search "<query>" --store pgvector --collection <name>` is the equivalent fallback against the same destination.

## Reporting

After any bootstrap or sync, report:

- Pipeline used or created.
- Files ingested.
- Verification queries and whether they returned the expected content.
- Anything that failed and why.

What this skill enforces

  • Auto-triggers on memory intent — the skill’s description covers explicit phrasing (“sync memory,” “save what we discussed”) and implicit memory-shaped questions (“what did I note about X”), so the user never has to remember to invoke it.
  • Declares its prerequisites — the metadata.openclaw block tells OpenClaw the skill needs the datris and docker binaries and offers a Homebrew install path (datris/tap/datris) if datris is missing. Override the default MCP endpoint via the MCP_SERVER_URL env var.
  • One file in = one upload — no bundling, no concatenation. Filenames round-trip into retrieval results so provenance is preserved and per-file incremental sync stays clean.
  • Parallel uploads, then poll — fires every upload_data call up front, collects job tokens, polls concurrently. No serial waiting.
  • Catalog isolation — every resource the agent creates lands in the openclaw data catalog by default, keeping OpenClaw’s footprint separate from other workloads on the same Datris instance.
  • Three-mode incremental sync — periodic timer sweep (default 30 min), end-of-response flush for agent-authored writes, and explicit user “sync memory” trigger. Renames go through delete-then-add so the index never accumulates orphans.
  • CLI fallback for resilience — MCP tools are the default, but the skill documents when to drop to datris health, datris ingest, datris search, and datris status — so a flaky MCP connection or a stalled poll doesn’t leave the user blocked.
  • Self-healing for inherited corpora — if a previous setup bundled everything into one file, the skill includes the reset-and-re-ingest workflow rather than papering over broken provenance.

Step 5 — Restart the OpenClaw gateway

Skills are discovered at gateway startup, so the new datris-memory skill won’t be active until you reload:
openclaw gateway restart
After the restart, OpenClaw will pick up ~/.openclaw/workspace/skills/datris-memory/skill.md and auto-trigger it on any memory-shaped request.

Step 6 — Kick off the bootstrap, then use it

With the skill loaded, point OpenClaw at it once so it runs the first-time bootstrap — health check, pipeline creation, parallel ingestion of every existing memory file, and verification. A simple opener works:
“You now have the datris-memory skill installed. Use it to ingest my existing memory into Datris.”
Watch the Agents tab in the Datris UI as it runs — you’ll see each upload_data call, pipeline_status poll, and vector_search verification stream in real time. When the bootstrap finishes, OpenClaw will write a summary into today’s memory/<date>.md audit log. After that, just talk normally. The skill auto-triggers on any memory-shaped request and reaches for Datris semantic search instead of grepping local files:
“What did I note about my preferred code review process?”
“Find anything in my memory related to onboarding new teammates.”
“Summarize what I’ve written down about meeting cadence.”

Advantages of this setup

Beyond the per-feature wins listed at the top, the architectural payoff of putting Datris between OpenClaw and your memory files is:
  • Recall quality scales with corpus size — adding more memory files makes retrieval better, not slower or noisier, because semantic search ranks by relevance instead of dumping everything into context.
  • Context budget stays tight — only the top-matching snippets land in the prompt, so long-running OpenClaw sessions don’t burn their window loading memory the agent doesn’t need.
  • One memory layer, many agents — point Claude Desktop, Claude Code, OpenClaw, or any other MCP-aware client at the same Datris pipeline and they all share the same long-term memory. No per-tool re-indexing.
  • Local-first, not cloud-locked — your markdown stays on disk, the index lives in your own Datris stack. You can wipe and rebuild the index any time without losing knowledge.
  • Memory becomes queryable, not just retrievable — because it’s a real Datris pipeline, you can run vector search, AI answer, and even SQL-shaped questions against your memory corpus from the CLI or REST API, not only from inside the agent chat.
  • Future memory is incremental — the skill’s three-mode sync (timer sweep, end-of-response flush, explicit user trigger) re-ingests only the files that changed, never the whole corpus.
The net effect: your agent stops forgetting, your memory files stop being a flat pile of markdown, and you get a real semantic layer over the knowledge you’ve been writing down all along.