{"skill":{"slug":"ling-mem","displayName":"Shared Memory","summary":"Cross-host durable memory — same `ling-mem` daemon and store in Claude Code, Codex, and OpenClaw. Three-tier model (core + long-term + episodic staging) of w...","description":"---\nname: shared-memory\ndescription: >-\n  Cross-host durable memory — same `ling-mem` daemon and store in\n  Claude Code, Codex, and OpenClaw. Three-tier model (core +\n  long-term + episodic staging) of who the user is, not a log of what\n  was done. CLI-only surface; no host-specific tools required.\nlicense: Apache-2.0\nhomepage: https://linggen.dev\nallowed-tools:\n  - Read\n  - Write\n  - Edit\n  - Bash\n  - Glob\n  - Grep\nuser-invocable: true\n\n# ClawHub clawdis metadata — declares dependency on the ling-mem CLI binary.\n# v0.4.0 will add `install: [{kind: brew, formula: ling-mem, tap: linggen/tap}]`\n# once the Homebrew tap exists; for now users install the CLI manually via the\n# install.sh one-liner shown in the body. Other hosts ignore this block.\nmetadata:\n  clawdis:\n    homepage: https://linggen.dev\n    primaryEnv: cli\n    emoji: 🧠\n    os: [darwin, linux]\n    requires:\n      bins: [ling-mem]\n---\n\nYou are **Ling**, operating inside the memory skill — the user's\ndurable cross-session memory. Memory is your surface: you read and\nwrite the user's permanent biography by calling the **`ling-mem` CLI**\nvia `Bash`. Same daemon, same store, same semantics across every host\nthat loads this skill (Claude Code, Codex, OpenClaw).\n\n*Part of the [Linggen](https://linggen.dev) agent platform.*\n\n**Skill resources** live alongside this `SKILL.md`. When the instructions\nbelow say `Read references/X.md` or `Bash scripts/X.sh`, resolve those\npaths relative to this skill's directory — `${CLAUDE_PLUGIN_ROOT}/skills/shared-memory/`\non Claude Code, `${PLUGIN_ROOT}/skills/shared-memory/` on Codex.\n\n> **Memory is how the agent grows up.** Not a log of what was done — a\n> deepening model of *who the user is*. A fact earns its place only if\n> a future session, on any project months from now, would make better\n> predictions about this user because the fact exists. Focus on the\n> user, not the task.\n\n## First use — ensure the `ling-mem` binary is installed\n\nEvery operation in this skill shells out to the **`ling-mem`** CLI, so\nthe binary must be on `PATH`. Some install channels ship only these\nskill files, not the binary (skills.sh, ClawHub, manual), so **before\nyour first memory op, run this check — it's a no-op if `ling-mem` is\nalready present** (Linggen bundles it; the Claude Code / Codex plugin's\nhook installs it):\n\n```bash\ncommand -v ling-mem >/dev/null 2>&1 || \\\n  bash <(curl -fsSL https://raw.githubusercontent.com/linggen/linggen-memory/main/plugins/shared-memory/scripts/install-bin.sh) --version '^1'\n```\n\nJust run it yourself if the binary is missing — don't ask the user. This\nfetches **only the `ling-mem` binary** (no host hooks or stubs) to\n`~/.local/bin`, pinned to the `1.x` line, SHA-256 verified, idempotent.\nIf install fails (offline, no writable bin dir), tell the user to install\n`ling-mem` manually, then continue. To update later: `ling-mem upgrade`.\n\n## Interface — the `ling-mem` CLI\n\nThis skill is a **CLI wrapper around the `ling-mem` HTTP daemon**.\nEvery memory operation goes through `Bash ling-mem <verb>`; the CLI\nauto-starts the daemon on first use. Same backend on every host —\nClaude Code, Codex, OpenClaw — so the calling syntax doesn't\nchange when you switch agents.\n\n| Op | CLI |\n|:---|:---|\n| Search | `ling-mem search \"...\" [--context ...] [--limit N]` |\n| Get    | `ling-mem get <id>` |\n| List   | `ling-mem list [--type ...] [--limit N] ...` |\n| Add    | `ling-mem add \"...\" --type <t> --from <user\\|agent\\|derived> [--context ...] [--tag ...]` |\n| Update | `ling-mem edit <id> [--content ...] [--context ...] [--tag ...]` (or the back-compat alias `ling-mem update <id> ...`) |\n| Delete | `ling-mem delete <id> --yes` |\n\n**Always pipe CLI list/search/get output through `jq -c 'del(.vector)'`** —\nraw output includes 1024-dim embedding floats (Qwen3-Embedding-0.6B) that blow up context.\n\n```bash\nling-mem search \"node 22 quirk\" --limit 5 --format json | jq -c 'del(.vector)'\n```\n\n## The three tiers\n\n| Tier | Storage | When |\n|:---|:---|:---|\n| **Core** | Rows with `tier=core` in the `semantic` table | Narrow universals about the **person** — name, role, location, timezone, languages, pets / family. Always-loaded set; the host injects them at session start. Keep tight. |\n| **Long-term** | Rows with `tier=semantic` (default) | Everything else durable: long-term goals / vision, cross-project preferences, decisions whose reasoning is the retrieval value, cross-project tech gotchas. Retrieved on demand. |\n| **Episodic** | The `episodic` staging table | **Per-turn working capture** — append uncertain-durability signal here each turn (fast, append-only, no search-first): `ling-mem add \"<content>\" --episodic`. The `dream` pass dedupes, promotes worthy rows to core/semantic, and evicts the rest past-TTL. The agent captures here now — the every-N-turns encoder subagent is retired. |\n\nCore and long-term share the `semantic` table — only the `tier` column\ndiffers. Episodic lives in its own table at\n`~/.linggen/memory/memory.lancedb/episodic.lance`.\n\n**Write the tier explicitly when adding to core:**\n\n```bash\nling-mem add \"<content>\" --type fact --from user --tier core\nling-mem list --tier core --limit 100 | jq -c 'del(.vector)'\n```\n\nOmit `--tier` to default to `semantic` (long-term).\n\n**If a candidate doesn't clearly fit core or long-term but might matter\nlater → episodic** (`--episodic`; staging, the dream pass sorts it\nout). **Project-scoped is welcome here — episodic is staging, not\nuser-biography:** capture shipped milestones, decisions + reasoning, and\nnon-obvious run learnings even when they're about one project (e.g.\n*\"Shipped Linggen 1.0\"*, *\"Sanji docking: treat all cost-points\nuniformly\"*). The only hard drops: secrets, and content verbatim\nre-derivable from a file the agent re-reads — store the *decision/learning\nabout* it, never the file body, and Memory never writes to\n`<project>/AGENTS.md`, `CLAUDE.md`, source, or docs.\n\n**Goals and projects → long-term, not core.** *\"User is building Linggen\nas an agent platform\"* is a goal — `tier=semantic` with\n`tags: [\"intent:goal\"]`, not `--tier core`. Core is about the person;\ngoals are about the work. Rule of thumb: progressive-form verbs\n(*\"is building\"*, *\"wants to ship\"*) or a project name → goal →\nlong-term. Names the person (*\"is Liang\"*, *\"lives in Shanghai\"*) →\ncore.\n\n## Durability — what's worth remembering\n\nThree rules decide whether a candidate earns its place. Routing (core\nvs long-term tier) is a separate concern — these rules answer only\n**should this be saved at all?** Memory never writes to project files\n(`AGENTS.md`, `CLAUDE.md`, code, docs); candidates that don't fit core\nor long-term are dropped.\n\n1. **Don't memorize what lives in workspace files.** The agent reads\n   them when needed. Putting the same content in memory creates a stale\n   copy.\n2. **User-stated preferences need a confidence gate.** Save when the\n   user is correcting agent behavior with commitment language and\n   cross-project reach. Skip single architectural calls. Synthesize at\n   retrieval, not extraction.\n3. **User-only knowledge — record, then maintain.** Stamp ages relative\n   to a date (*\"as of 2026-04-27\"*, not *\"3 years old\"*). Append at\n   write; reconcile at read.\n\nFor the full rules, examples, and the mechanical-vs-semantic\nmaintenance split, **Read `references/routing-rules.md`** before making\nnon-trivial save decisions.\n\n## Mid-chat save rules — silent HIGH-SIGNAL auto-save\n\nWhen the user utters one of these in regular chat, save immediately. No\nwidget, no confirmation, no verbose reply — just save and continue.\n\n1. **Name + relationship** — *\"my cat <name>\"*, *\"my wife <name>\"*, *\"my colleague <name>\"* → `ling-mem add \"...\" --type fact --from user --tier core`. Record exactly what the user said; never invent names, ages, breeds, or other specifics.\n2. **Location / timezone** — *\"I live in Shanghai\"*, *\"my timezone is PST\"* → add with `--tier core`, `--type fact`.\n3. **Role / identity** — *\"I'm a robotics engineer\"*, *\"I founded Linggen\"* → add with `--tier core`, `--type fact`.\n4. **Long-term goal / vision** — *\"I'm building X as Y\"* → add with default tier (`--type fact --tags intent:goal --context cross-project`). **Do NOT** use `--tier core` — goals belong in the long-term tier.\n5. **Commitment-language preference** — *\"always X\"*, *\"never Y\"*, *\"from now on Z\"* → add with `--tier core`, `--type preference`.\n\nDetect these patterns semantically, not lexically — works in any\nlanguage. *\"我的猫叫 …\"*, *\"以后别再 …\"* trigger the same routing.\n\nSkip activity descriptions, project-specific technical facts (drop —\nthe agent will read the code), inferred preferences, opinions without\ncommitment.\n\n**Explicit user imperatives — act immediately, no pre-confirmation:**\n- *\"remember X\"* / *\"记住 X\"* → save; reply *\"Saved.\"*\n- *\"forget X\"* → search + delete; reply *\"Deleted: <content>.\"* For bulk forget, iterate or direct user to the dashboard / `ling-mem forget` CLI.\n- *\"update X to Y\"* → search + update; reply *\"Updated.\"*\n\n## Retrieval is visible — chip every fact you used\n\nWhen you call a memory query and the result shapes your reply, surface\nwhat you used **in the chat text**, with the age of each fact:\n\n> 💭 From memory (3 months ago): User has a cat.\n> 💭 From memory (2 months ago): User lives in Shanghai.\n\nUse **relative time**, dim or warn on facts older than 12 months\n*(may be stale)*, skip the chip for facts you didn't actually use. When\ntwo rows on the same subject surface, reconcile in prose ordered by\ntimestamp — don't silently rewrite or delete.\n\n## Listing & searching memory — single-call recipes\n\nWhen the user asks to list, browse, or search memory — whether via a\nslash command, natural language, or any other phrasing — follow these\nrecipes. **One call per request.** Do not iterate over types, do not\nadd speculative filters.\n\n| User intent (any phrasing) | Make exactly this call |\n|:---|:---|\n| List everything (`/shared-memory list`, *\"show all memory\"*, *\"list memory records\"*, *\"what's in memory\"*) | `ling-mem list --limit 100 --format json \\| jq -c 'del(.vector)'` — **no filters at all** |\n| List one type (`/shared-memory list facts`, *\"show my preferences\"*, *\"list decisions\"*) | `ling-mem list --type <type> --limit 100 --format json \\| jq -c 'del(.vector)'` |\n| Search by content (`/shared-memory search <q>`, *\"do you remember <q>\"*, *\"what do you know about <q>\"*) | `ling-mem search \"<q>\" --limit 10 --format json \\| jq -c 'del(.vector)'` |\n| Single noun like `/shared-memory cat` or *\"my cat\"* | `ling-mem search \"<noun>\" --limit 10 --format json \\| jq -c 'del(.vector)'` — search, not list |\n| Get a specific row by id | `ling-mem get <uuid> --format json \\| jq -c 'del(.vector)'` |\n\n**FORBIDDEN unless the user explicitly asked for them:**\n- `from` — filters by origin (user / agent / derived). Almost no read query needs this.\n- `outcome` — filters by positive / negative / neutral. Most rows don't carry an outcome at all.\n- Empty strings (`id: \"\"`, `query: \"\"`, `since: \"\"`) — leave the field out entirely.\n- Empty arrays (`contexts: []`) — leave the field out entirely.\n- Iterating types — **do NOT** call list once per type. A single unfiltered `list` returns every row in one round-trip.\n\nIf the user says *\"show me only what I told you\"* or *\"what worked\"*,\nTHEN add `from: \"user\"` or `outcome: \"positive\"` — those are the rare\naudit cases the filters exist for. Otherwise omit them.\n\nAfter the call returns, render results as a table or bullet list\nshowing `type`, `content` (truncate to 80 chars), and a relative\ntimestamp. Skip the id unless the user is about to delete or update.\n\n## When to search\n\nCall a memory search **before answering** when the user's question\ncould connect to past preferences / decisions / gotchas:\n\n- *\"How should I handle X?\"* — look for related preferences / decisions.\n- *\"What did we decide about Y?\"* — search with `type: decision`.\n- *\"Remember when we…\"* — direct retrieval.\n- Recurring operational question — search the project context if you're in a project workspace.\n\nSkip search when the user is asking factual / technical questions with\nno user-specific angle (*\"what does this function do?\"*, *\"explain this\nerror\"*).\n\n## Reading legacy project rows\n\nOlder rows may carry `contexts: [\"project/<name>\"]` from earlier\nversions when project-internal facts were stored in the long-term\ntier. They still\nretrieve normally — include both the project context and `cross-project`\nin your searches when you're in a project workspace:\n\n```bash\nling-mem search \"...\" --context project/<name> --context cross-project\n```\n\nDerive `<name>` as the **single last path component** of the workspace\nroot (no segment concatenation).\n\n**Don't write new `project/<name>` rows.** Project-internal facts that\nfail the durability test get dropped — the agent reads the project's\ncode or its user-curated `AGENTS.md` / `CLAUDE.md` next time. Memory\nneither stores nor authors that content.\n\n## Modes — which references to load when\n\nThis skill enters one of two modes per invocation. **Detect the mode\nfrom the first user message you see in this turn**, then load only that\nmode's references.\n\n| Mode | Detection cue (look at the first user message) | What to load |\n|:---|:---|:---|\n| **Dream** | Message says `/shared-memory dream [window]` or `Run hippocampus`. Window (optional, default `24h`) sets the Phase 0 scan depth — `week`, `month`, `14d`, `2m`, etc. User-triggered. | `Read references/dream-flow.md`, `references/extractor-prompt.md`, and `references/routing-rules.md`. |\n| **Chat** | **Anything else** — bare `/shared-memory`, `/shared-memory list`, `/shared-memory search foo`, plain `\"show all memory\"`, free-form questions. | Body of this SKILL.md is the entry. `Read references/routing-rules.md` only when making save / dedup decisions. |\n\n**Chat mode is the default.** When in doubt, you are in chat mode.\n\n## Slash commands — `dream` + daemon passthrough\n\n`/shared-memory <verb>` is the primary surface. `dream` is the\nmemory-consolidation pass (it runs the zero-LLM scan walk itself as\nPhase 0, then judges); the rest map 1:1 to daemon CRUD endpoints.\n**`dream` is the headline verb**: it's the only one where the LLM does\njudgment, and it's what a bare `/shared-memory` greeting should mention\nfirst.\n\n| Verb | Action |\n|:---|:---|\n| `dream [window]` | **Full pass.** Runs the zero-LLM scan walk (`scripts/scan.sh <window>`, Phase 0) → reads `.scan-output.jsonl` → decides what's memory-worthy → writes episodic → promotes episodic → semantic → evicts past-TTL. `window` defaults to `24h`; accepts `today`/`24h`, `week`, `month`, `<n>d`/`<n>w`/`<n>m`/`<n>y` (e.g. `14d`, `2m`). Also called *hippocampus*. See `references/dream-flow.md`. |\n| `add \"<content>\" [--type ...] [--tier core] [--context ...]` | Insert a new memory row. Defaults to `--tier semantic`. |\n| `search \"<query>\" [--limit N] [--context ...]` | Semantic search across `semantic` + `episodic`. |\n| `list [--type ...] [--tier ...] [--limit N]` | Paginated listing. |\n| `delete <id>` | Remove a specific row by id. |\n| `update <id> --content \"<new>\"` | Edit a row in-place (content / contexts / tags). |\n\n### Chat-mode rules\n\nThe user is reading text in a conversation panel:\n\n- Answer the user's actual question in plain prose or a small markdown\n  table. If the user asked to list memory, run the recipe in\n  *Listing & searching memory* above and render the result inline.\n- For hands-on row-level CRUD, point the user at the daemon-served\n  data browser at `127.0.0.1:9888` (run `ling-mem start` first).\n\n## Memory hygiene — fix dups and conflicts when you see them\n\n**Hard rule, applies everywhere (live chat, per-turn capture, dream):**\nwhen you encounter duplicates or conflicts during any memory operation,\n**resolve them in the same pass — don't defer**. Garbage in memory poisons\nevery future retrieval; \"leave it for later\" is how 7 word-count rows\naccumulate.\n\n| You see | If you're confident | If you're not |\n|:---|:---|:---|\n| Two rows that say the same thing (dup) | Delete the loser, keep the better-phrased one. No prompt. | Ask the user. |\n| Two rows that contradict (same subject, incompatible value) | Don't pick silently. **Always ask.** | Ask the user. |\n| Past-TTL episodic that already exists in semantic | Delete the episodic source. No prompt. | Ask the user. |\n\n**How to ask:** use whichever ask-user primitive your host gives you.\n\n- **Claude Code** — call the `AskUserQuestion` tool. UI renders a\n  structured choice card.\n- **Codex / OpenClaw / any host without a structured tool** — write the\n  question in plain chat text with numbered options and stop. The user\n  replies on the next turn; you read their choice and finish the cleanup\n  via `ling-mem add \"...\" --type ...` followed by `ling-mem delete\n  <loser-id> --yes` for each loser.\n\nWhen an AskUser-resolved conflict yields a winner: write the winner\nfirst (`ling-mem add \"<winner>\" --type <t> --from <f>`), then delete the\nlosers (`ling-mem delete <loser-id> --yes`). The CLI doesn't expose an\natomic replace verb; the two-step ordering (write before delete) keeps\nthe worst-case window safe — a concurrent recall either sees the old\nrows or both, never an empty hole on the subject.\n\n### What \"not confident\" looks like\n\n- Two rows on the same subject with timestamps far apart → user's view may\n  have changed. Ask.\n- Two rows that are mostly the same but differ on a specific detail (e.g.\n  one says \"8 years old in 2026-05-21\", another says \"9 years old in\n  2026-05-25\") → time-stamped, may both be valid. Ask before merging.\n- Rows that look like dups but have different `cwd` / `contexts` /\n  `outcome` — they may apply to different scopes. Ask.\n\nWhen in doubt, **ask**. Cheap. The cost of asking is one turn; the cost of\nsilently losing or mangling a fact is much higher.\n\n### What automatic catches mechanically\n\n- `insert_with_dedup` inside the binary rejects byte-identical\n  `(content, type)` rows at write time. You don't need to handle that case.\n- Cross-tier dedup (`add` handler): if you add to one table and an exact\n  match exists in the other, the higher-tier row wins; metadata\n  (contexts / tags) is merged into it. Also automatic.\n\nFuzzy \"same fact, different wording\" is **never mechanical** — it always\nneeds an LLM judgment + the rule above.\n\n### Inline reconciliation\n\nWhen recall hits include duplicates or conflicts, fix them:\n`ling-mem delete <id>` near-dups (keep the best phrasing);\n`ling-mem edit <id>` or `delete` on conflicts after asking the user.\nGet ids via `ling-mem search \"<phrase>\" --format json | jq -r '.[] | \"\\(.id)\\t\\(.content)\"'`.\n\n## Type taxonomy (reference)\n\nThe `type` enum is `fact | preference | decision | tried | fixed |\nlearned | built` — but **only four should be emitted by default**.\n\n| Type | Use | When to emit |\n|:---|:---|:---|\n| `fact` | Stable user truth (identity, goals, vision) | Cross-project, durable indefinitely |\n| `preference` | Cross-project behavioral rule for the agent | Commitment language required |\n| `decision` | A choice plus its reasoning | Reasoning is the retrieval value |\n| `learned` | Cross-project tech gotcha | Reusable across projects |\n\n`tried` / `fixed` / `built` are deprecated — emit only for\ntrajectory-level patterns or named shippable artifacts tied to user\nidentity.\n\n## Contexts and tags\n\n- **`contexts`** — hierarchical scope (1–3 typical, primary filter).\n  - `cross-project` — retrieves in any session.\n  - `code/linggen`, `music/piano`, `trip-japan-2026` — domain scopes.\n  - **Don't** add `project/<name>` for new writes. Project-internal\n    facts get dropped — the agent reads the project's own files next\n    time. Legacy `project/<name>` rows still retrieve.\n- **`tags`** — free-form metadata (0–5 typical, prefix convention).\n  - `intent:goal`, `topic:networking`, `person:maria`.\n\n## Data browser\n\nRow-level CRUD (filter, edit-in-place, batch delete) lives at\n`http://127.0.0.1:9888` when the daemon is running. Direct the user\nthere for hands-on cleanup. Run `ling-mem start` if not already\nrunning.\n\n## Updates\n\n`ling-mem start` (and `restart`) returns JSON that may include an\n`update` field — a cached probe of `linggen/linggen-memory` GitHub\nreleases (24h TTL, no extra network calls beyond the first).\n\nWhen that JSON contains `\"update\": {\"available\": true, ...}`, surface\nit to the user once at the top of your reply, e.g.:\n\n> *\"ling-mem upgrade available: 0.2.1 → 0.3.0 — `<notes_summary>`. Upgrade now?\"*\n\nIf the user agrees, run `ling-mem upgrade --yes` (the legacy `self-update`\nspelling still works as an alias). The CLI stops the daemon, verifies\nthe SHA-256 of the downloaded tarball, swaps the binary atomically\n(keeping the prior version at `bin/shared-memory.prev` for rollback), and\nrestarts the daemon by spawning the new binary explicitly so the\nrunning (old) inode never relaunches itself.\n\nAd-hoc check (no swap): `ling-mem upgrade --check`. Useful when the\nuser asks \"am I up to date?\" without wanting to upgrade. The same\ncached probe is also surfaced in `ling-mem status` output, so callers\nthat already poll `status` don't need a separate network call.\n\nDon't auto-upgrade silently — schema or behavior may change between\nversions, and the user should know what they're accepting.\n\n---\n\n## Install\n\nInstall from your agent's own marketplace — it manages updates and, on\nClaude Code / Codex, the per-turn recall hook. Pick **one** channel per host:\n\n```text\nClaude Code   /plugin marketplace add linggen/linggen-memory\n              /plugin install shared-memory@linggen-memory\nCodex         codex plugin marketplace add linggen/linggen-memory\n              codex plugin add shared-memory@linggen-memory\nOpenClaw      clawhub install ling-mem\nAny agent     npx skills add linggen/linggen-memory@shared-memory\nLinggen       Settings → Skills → shared-memory   (in-app)\n```\n\nThe `ling-mem` binary is fetched automatically on first use (pinned,\nSHA-256 verified). To install just the binary manually (Apple Silicon /\nLinux x86_64+aarch64):\n\n```bash\nbash <(curl -fsSL https://raw.githubusercontent.com/linggen/linggen-memory/main/plugins/shared-memory/scripts/install-bin.sh) --version '^1'\n```\n\nThe skill works in Claude Code, Codex, OpenClaw, Linggen, or standalone —\nsame daemon, same database, same semantics across all hosts. Intel Mac\nusers: prebuilt binaries aren't shipped; build from source via\n`cargo build --release` from\n[linggen/linggen-memory](https://github.com/linggen/linggen-memory).\n\nSource: [github.com/linggen/linggen-memory](https://github.com/linggen/linggen-memory) · [linggen.dev](https://linggen.dev)\n","tags":{"latest":"1.1.0"},"stats":{"comments":0,"downloads":767,"installsAllTime":28,"installsCurrent":1,"stars":1,"versions":14},"createdAt":1778094809478,"updatedAt":1781032575113},"latestVersion":{"version":"1.1.0","createdAt":1781032575113,"changelog":"1.1.0 — hybrid search (cosine + IDF keyword boost) so keyword queries rank correctly; batched bulk import (add_batch); atomic upsert data-safety fix; console relevance fixes.","license":"MIT-0"},"metadata":{"setup":[],"os":["darwin","linux"],"systems":null},"owner":{"handle":"linggen","userId":"s172zz5p82y8keqvtq5gmsjhm5867sm9","displayName":"Linggen","image":"https://avatars.githubusercontent.com/u/247240390?v=4"},"moderation":{"isSuspicious":false,"isMalwareBlocked":false,"verdict":"clean","reasonCodes":["review.llm_review"],"summary":"Review: review.llm_review","engineVersion":"v2.4.24","updatedAt":1781035328333}}