DeepSeek Harness plugin

dsh-memory-tdai

Four-layer long-term memory for DeepSeek Harness (L0 conversation → L1 atoms → L2 scenes → L3 persona) with auto-recall and auto-capture — adapted from TencentCloud/TencentDB-Agent-Memory

Jump to install

Source facts

Repository
Jason-Liao/dsh-memory-tdai
Latest update
Aug 16, 2026
Category
Workflow & Automation
GitHub stars
0
Format
plugin
Catalog evidence
Upstream dsh.bundle evidence
Evidence path
package.json#dsh.bundle
Checked against
0.1.0-rc.8
Upstream check date
2026-08-20

This evidence comes from the upstream catalog. This site has not installed, run, or security-reviewed the plugin.

Install

Start with a prompt that asks an agent to review the GitHub repository and source. Switch to the command if you want to install it yourself.

Copy this prompt into DSH, Codex, or another agent and ask it to review the GitHub repository and source first.

Do not install or run any commands yet. Read this plugin's GitHub repository, README, and relevant source code. Then answer the questions below clearly and directly so I can decide whether it fits my needs:

1. What is this plugin, and what problem does it solve?
2. Who is it for, and what are its typical use cases?
3. How is it used after installation? Include one minimal example.
4. What known limitations or privacy, security, compatibility, or maintenance risks does it have?
5. Give a clear recommendation: recommend, conditionally recommend, or do not recommend, with reasons.

Distinguish statements documented by the repository, inferences from source code, and unknowns. If evidence is insufficient, say so explicitly. Do not guess or simply repeat the README.

GitHub: https://github.com/Jason-Liao/dsh-memory-tdai
Plugin: dsh-memory-tdai
Author: Jason-Liao

Check the source files

Read the README and other files from this plugin directory before installing.

File explorer4 files
README_EN.mdSource · read only
README language

dsh-memory-tdai

English | 中文

Four-layer long-term memory for DeepSeek Harness — a complete DSH port of TencentDB-Agent-Memory from the OpenClaw runtime.

Give your DeepSeek agent a real memory: conversations are automatically distilled into a four-layer structure — raw records → atomic memories → scene blocks → persona profile — with cross-session retrieval, automatic recall, and automatic archival. The model no longer needs to re-read your whole history to know your preferences, habits, and project background.

L0 raw conversations ──▶ L1 atomic memories ──▶ L2 scene blocks ──▶ L3 persona profile
      (jsonl)              (SQLite + vectors)    (scene_blocks)       (persona.md)
    auto-capture            extract/dedup         scene induction        user profile

---

📖 Where this plugin comes from

This plugin is a DSH port of TencentCloud/TencentDB-Agent-Memory (Tencent Cloud's official open-source project, MIT licensed).

The upstream project built a four-layer memory system for OpenClaw (another agent runtime). Official benchmarks: task success up to +51.5%, token usage down to −61.4%, persona-memory accuracy up from 48% to 76%. Its core engine TdaiCore was deliberately designed to be host-neutral: all storage, extraction, scene, and persona logic depends on a tiny 4-method HostAdapter interface — OpenClaw was just one shell around it.

How the port works (swap the shell, keep the engine)

OpenClaw surfaceDeepSeek Harness surface
api.registerTool("tdai_memory_search")tools.register("memory_search")
api.registerTool("tdai_conversation_search")tools.register("conversation_search")
api.on("before_prompt_build")agent/pre-step waterfall + dynamic systemPrompt section
api.on("agent_end")agent/turn-stopping
api.on("gateway_stop")ctx.effect disposer → core.destroy()
OpenClawLLMRunner (own API key)DshLLMRunner over DSH's ctx.llm (zero-config)
openclaw.plugin.json configbundle row config + DSH_MEMORY_* env

Three things we actually did:

1. Kept the core, replaced the shell: TdaiCore (four-layer pipeline, SQLite vector store, BM25/FTS5 retrieval, prompt engineering) is carried over untouched; a ~100-line DshHostAdapter implements the 4-method interface. 2. Zero-config LLM reuse: extraction/scene/persona model calls ride DSH's own ctx.llm service — the same DeepSeek route and credentials you already use. No extra API key required. This is the most practical difference from upstream. 3. Event mapping: OpenClaw's before_prompt_build → DSH agent/pre-step + a dynamic systemPrompt section, so recalled memories enter the system prompt without polluting the conversation history; agent_endagent/turn-stopping persists each turn as soon as it closes.

What was cut compared to upstream

ItemUpstream (OpenClaw)This DSH version
LLM calls@ai-sdk/openai + separate API keyDSH ctx.llm reuse, zero config
Vector depstcvdb-text (286MB wasm dict) + sqlite-vecsqlite-vec + FTS5 required; tcvdb-text optional lazy-load (auto-degrade BM25→FTS5)
Local embeddingnode-llama-cpp (GGUF)code kept; auto-degrades to keyword retrieval when unconfigured
OpenClaw-only codeCleanContextRunner / hook policy / CLIremoved
Memory capabilityL0/L1/L2/L3 full pipelinefull pipeline kept

---

✨ Features

Automatic memory accumulation (zero effort)

  • Auto-capture (L0): every finished turn is appended to

conversations/YYYY-MM-DD.jsonl — nothing the model, tools, or you said is lost.

  • Auto-extract (L1): a background pipeline distills conversations into

atomic memories (e.g. "User prefers replies in Chinese"), stored in a SQLite vector store with type (persona / episodic / instruction), priority, and scene attribution — with smart dedup.

  • Auto-induce (L2): atomic memories cluster into scene blocks

(scene_blocks/).

  • Auto-profile (L3): once scenes accumulate past a threshold, a

persona.md user profile is generated and stays stable across sessions.

Automatic recall (zero effort)

  • Before every model step, the plugin retrieves memories relevant to the

current topic and injects them through a dynamic system-prompt section (<relevant-memories>) — the model "just knows" your history.

  • Recalled content never enters the session history (no repeated memory spam

in context).

Two model tools (on demand)

ToolPurposeWhen to use
memory_searchSearch structured memories (persona/events/rules; FTS5+BM25+vector hybrid ranking)User preferences, historical facts, established rules
conversation_searchSearch raw conversation records (exact messages with provenance)Exact wording, what was said when

Retrieval capabilities

  • Hybrid retrieval: with an embedding endpoint configured, "vector +

keyword" (RRF fusion); without one, it auto-degrades to FTS5 keyword search with jieba Chinese tokenization.

  • Scene navigation: L2 scene blocks can be retrieved, expanded, and

replayed.

  • Fully traceable: persona → scene block → atomic memory → raw

conversation, drill down layer by layer without losing evidence.

---

📦 Install

Prereqs: dsh installed (any open-source build), pnpm on PATH (corepack enable or npm i -g pnpm).

# Option 1: from GitHub (no npm account needed)
dsh plugin --profile web add https://github.com/Jason-Liao/dsh-memory-tdai/archive/refs/tags/v1.0.0.tar.gz

# Option 2: from npm (once published)
dsh plugin --profile web add dsh-memory-tdai

# Option 3: local tarball for development
cd dsh-memory-tdai && npm pack
dsh plugin --profile web add ./dsh-memory-tdai-1.0.0.tgz

Then restart dsh web and hard-refresh the browser. dsh plugin automatically appends this package to dsh.profile.bundles (it declares dsh.bundle) — no manual config edits.

Verify: in a new session ask the model "use memory_search to check my memories" — success means the tool is in its toolset.

---

🚀 Usage

Out of the box (default config)

1. Just chat — conversations are recorded and distilled automatically. 2. Cross-session questions — in a new session, ask "do you remember how I like to work?"; the model uses auto-recall or memory_search. 3. Manual lookups — "what did we discuss about X last week?" → the model calls conversation_search and finds the exact original message.

Where the data lives

~/.dsh/memory-tdai/
├── conversations/     L0 raw conversations (daily jsonl)
├── records/           L1 atomic memories (SQLite vectors.db + index)
├── scene_blocks/      L2 scene blocks
├── persona.md         L3 persona profile
└── .metadata/         pipeline cursors

Deleting the whole directory wipes all memory (be careful).

---

⚙️ Configuration

Zero-config by default. Override via bundle row config (cordis.patch.yml or --patch) or environment variables:

SettingDefaultEnv var
Data directory$DSH_HOME/memory-tdaiDSH_MEMORY_DATA_DIR
LLM providerdeployment default (deepseek-official)DSH_MEMORY_PROVIDER
LLM modeldeployment default (deepseek-v4-flash)DSH_MEMORY_MODEL
Auto-captureonDSH_MEMORY_CAPTURE_ENABLED (0/1)
Auto-recallonDSH_MEMORY_RECALL_ENABLED (0/1)
L1 extractiononDSH_MEMORY_EXTRACTION_ENABLED (0/1)

Pipeline pacing (bundle row config, see upstream config.ts): pipeline.everyNConversations (default: trigger L1 every 5 rounds, warmup progressive), persona.triggerEveryN (default: regenerate persona every 50 memories), etc.

---

🧠 Embedding configuration (for advanced users)

> Zero-config runs in keyword mode. For semantic retrieval (vector + > keyword hybrid, RRF fusion ranking), configure an OpenAI-compatible > embedding endpoint. DeepSeek's official API has no embedding endpoint — > use any third-party OpenAI-compatible service.

Full field reference

Under the embedding group of the bundle row config (~/.dsh/profiles/web/cordis.patch.yml, add config to the memory-tdai row):

FieldRequiredDefaultNotes
enablednotrueMaster switch (still disabled when provider: none)
provideryesnoneAny value other than none/local (e.g. openai, dashscope) is treated as an OpenAI-compatible remote; qclaw routes through a local proxy
baseUrlyesCompatible endpoint base URL, e.g. https://dashscope.aliyuncs.com/compatible-mode/v1
apiKeyyesEndpoint key
modelyesModel id, e.g. text-embedding-v4, text-embedding-3-small, BAAI/bge-m3
dimensionsyesVector dimensions — must match the model output (table below); a mismatch corrupts the vector table / returns empty results
sendDimensionsnotrueWhether to send the dimensions field in the request body. OpenAI text-embedding-3-* supports it (Matryoshka); OSS models like BGE-M3 reject unknown fields (HTTP 400) → set false
maxInputCharsno5000Truncation threshold per text
timeoutMsno10000Per-request timeout
recallTimeoutMsnosameRecall-path timeout (user-facing, should be shorter)
captureTimeoutMsnosameCapture-path timeout (background, may be longer)
conflictRecallTopKno5Candidate recall count for L1 dedup
proxyUrlnoOnly for provider="qclaw" (local proxy forwarding)

Endpoint examples

# ~/.dsh/profiles/web/cordis.patch.yml
- insert:
    - id: memory-tdai
      name: 'dsh-memory-tdai'
      config:
        embedding:
          enabled: true
          provider: dashscope
          baseUrl: https://dashscope.aliyuncs.com/compatible-mode/v1
          apiKey: sk-xxxxxxxx
          model: text-embedding-v4
          dimensions: 1024
          sendDimensions: false
EndpointbaseUrlExample modeldimensionssendDimensions
Aliyun Bailianhttps://dashscope.aliyuncs.com/compatible-mode/v1text-embedding-v41024false
Aliyun Bailiansameqwen3-embedding-0.6b / qwen3-embedding-4b1024 (tunable)false
OpenAIhttps://api.openai.com/v1text-embedding-3-small1536true
SiliconFlowhttps://api.siliconflow.cn/v1BAAI/bge-m31024false
vLLM/Ollama self-hostedyour gatewayany compatible modelper modelfalse

Retrieval strategy (recall group)

recall.strategy, one of three (default hybrid):

ValueBehaviorBest for
hybridvector + keyword (FTS5) dual recall, RRF fusionrecommended default
embeddingvector-only recallembedding configured, semantically dense corpus
keywordkeyword-only (FTS5 + jieba)no embedding, or exact-term/code retrieval
config:
  recall:
    strategy: hybrid        # embedding | keyword | hybrid
    maxResults: 5           # max memories per recall
    scoreThreshold: 0.3     # relevance threshold
    maxTotalRecallChars: 0  # total injection cap (0 = unlimited)

⚠️ Notes

1. dimensions mismatch = empty retrieval: the vector table is built at that dimension; wrong values break writes or return nothing. After changing dimensions/model the store detects the provider change and triggers a full re-embed of historical memories (time depends on corpus size). 2. apiKey is plaintext in cordis.patch.yml for now. Don't commit your profile directory to git if that matters; DSH credentials service integration is planned for a future release. 3. qclaw mode: provider="qclaw" forwards requests through proxyUrl (Tencent Cloud vector-gateway scenario). 4. Verify: after restart, logs should show Store created: ... embedding=enabled; search memory_search with a semantically related but literal-word-free query (e.g. memory contains "coffee", query "wake-up drinks") — a hit means vectors are active.

---

🔬 How it works (deep dive)

  • Event-sourced capture: on agent/turn-stopping, incremental session

messages are read (deduped by a message-count cursor) and written to L0.

  • LLM reuse: DshLLMRunner calls ctx.llm.stream() with the same route

as the conversation; L2/L3 "tool-enabled" runs use a sandboxed file-tool loop (read/write/replace confined to the memory workspace).

  • Dynamic prompt section: recall is injected via a systemPrompt.section

provider function — fresh cache read at every assembly; the model sees the memories, the history stays clean.

  • Graceful degradation: if any of sqlite-vec / jieba / tcvdb-text /

node-llama-cpp is missing, it degrades instead of crashing (vectors → keyword; BM25 → FTS5).

---

🛠️ Development & build

git clone https://github.com/Jason-Liao/dsh-memory-tdai.git
cd dsh-memory-tdai
npm i
node build.mjs          # esbuild bundles src → lib/index.js
node test/smoke.mjs     # smoke test: mock LLM + real SQLite, capture→extract→recall→search

Layout: src/ (TypeScript: upstream core/ + new adapters/dsh/ + plugin entry index.ts), cordis.patch.yml (bundle layer), lib/index.js (built artifact for release).

---

⚠️ Known limitations (read first)

1. Without an embedding endpoint, retrieval is keyword-based, not semantic

Two retrieval engines, very different capabilities:

EngineHow it worksExample (memory: "User likes coffee")
Keyword (default)literal term matching (jieba tokenization + SQLite FTS5)ask "coffee" ✅ hit; ask "what do I drink to wake up" ❌ miss (no literal "coffee")
Vector (needs config)embeddings, semantic distanceask "wake-up drinks" ✅ also hits "coffee"

Why: embedding requires an embedding API (text → vector service). DeepSeek's official API currently has no embedding endpoint, and the plugin won't silently call third-party services. So the default is keyword mode — usable, but literal.

Upgrade: provide an OpenAI-compatible embedding endpoint in the bundle row config (OpenAI text-embedding-3-small, Aliyun Bailian qwen3-embedding, SiliconFlow, self-hosted vLLM/Ollama, etc.). The plugin then switches to "vector + keyword" hybrid (RRF fusion) with much stronger semantic matching. Not configuring it doesn't break anything.

> jieba is the Chinese tokenizer — "我喜欢喝咖啡" is split into > "我/喜欢/喝/咖啡" before indexing, which is exactly how FTS5 matches Chinese. > Keyword mode is already usable for Chinese; it just can't do > synonym-level semantic matching.

---

2. L1 extraction is background/progressive — the first few turns return no structured memory

L0 raw conversations are persisted every turn (plain local file writes, zero cost). But L1 atomic memories (distilling conversations into structured facts) requires one LLM call per extraction (token cost), so it does not run every turn.

Pacing (warmup): by default L1 triggers every 5 turns; to show value quickly the threshold ramps 1 → 2 → 4 → 5 (the first run happens after turn 1, then doubles, settling at every 5).

Impact:

  • Right after install, memory_search (structured memories) may return empty

for a few turns.

  • conversation_search always works — L0 is persisted every turn.
  • After a day of normal use, all layers accumulate naturally.

---

3. This plugin cannot be loaded as a "dynamic plugin" — bundle install only

WayDescriptionThis plugin
Bundle (recommended)<br>dsh plugin --profile web add ...loaded as an npm package by the Loader, runs in the real Node process, normal node_modules access✅ the only correct way
Dynamic plugin<br>in-session cordis_define / cordis_runcode runs in a restricted vm sandbox: require forbidden, native modules unavailable❌ unsupported

Why: this plugin depends on two native modulessqlite-vec (SQLite vector extension, .node binary) and jieba tokenization. The vm sandbox has neither. Use the bundle channel (install section); dsh plugin automatically adds it to dsh.profile.bundles.

---

4. Other edge notes

  • Local-first data: everything lives in $DSH_HOME/memory-tdai/ (see

Usage); deleting the directory wipes all memory — copy it when migrating machines.

  • Optional deps: BM25 sparse encoding needs

@tencentdb-agent-memory/tcvdb-text (~286MB, jieba-wasm dict) — not installed by default, auto-degrades to pure FTS5; local embedding needs node-llama-cpp (GGUF models) — also optional. Both missing still runs fine.

  • Session ownership: memories are isolated by DSH session key;

cross-session retrieval works for the same user. Subagent sessions are also captured in this DSH version (upstream's excludeAgents filter is not yet wired in — planned).

---

📄 License

MIT — derived from TencentCloud/TencentDB-Agent-Memory (MIT). Full text in [LICENSE](./LICENSE).