```
Some checks are pending
Pre-commit / run (ubuntu-latest) (push) Waiting to run

feat: add Claude Code marketplace plugin with service and expert tiers

Add comprehensive Claude Code marketplace plugin supporting two paradigms
for managing markdown vaults:

- reme-service: Service-tier plugin with high-level MCP tools
  (retrieve/remember/maintain) where reme2 internals handle R-M-W loop
- reme-expert: Expert-tier plugin where Claude Code agent runs R-M-W loop
  directly using raw memory_* primitives guided by reme protocol skill

Both plugins implement identical 4-phase work paradigm:
- Recall: Retrieve relevant context with ranking by relevance/proximity
- Log: Record event facts and raw materials via idempotent event-folder upsert
- Distill: Promote events to topic graph via R-M-W loop
- Maintain: Vault hygiene sweep with lint and decay operations

Include marketplace configuration, documentation, MCP server setup,
subagents (reme-distiller, reme-curator), hooks (PreCompact, SessionEnd,
Stop), and slash commands (/reme-distill, /reme-recall, /reme-clean).

Remove outdated plugin entries from gitignore.
```
This commit is contained in:
huangsen 2026-05-11 10:58:40 +08:00
parent dd2de16481
commit ad6368fdf0
22 changed files with 1309 additions and 2 deletions

2
.gitignore vendored
View file

@ -44,5 +44,3 @@ meta_memory/*
memories/*
.reme/*
/vault
/reme-plugin
/reme2/vault

View file

@ -0,0 +1,20 @@
{
"name": "reme-marketplace",
"owner": {
"name": "huangsen"
},
"plugins": [
{
"name": "reme-service",
"source": "./plugins/reme-service",
"description": "Service-tier markdown vault — three high-level MCP tools (retrieve / remember / maintain). reme2 internals own the R-M-W loop; the agent just hands off material.",
"version": "0.1.0"
},
{
"name": "reme-expert",
"source": "./plugins/reme-expert",
"description": "Expert-tier markdown vault — Claude Code's agent runs the R-M-W loop directly using raw memory_* primitives, guided by the reme protocol skill + reme-distiller / reme-curator subagents.",
"version": "0.1.0"
}
]
}

122
reme-plugin/README.md Normal file
View file

@ -0,0 +1,122 @@
# reme — Claude Code marketplace
Two paradigms for managing a markdown vault from Claude Code, packaged as
two installable plugins. **The 4-phase work paradigm is identical
across both** — only the locus of computation for the LLM-driven phases
(Distill, Maintain) differs.
## The 4-phase paradigm (both plugins)
Every interaction with the vault falls into one of four phases:
| Phase | What | Trigger |
|---|---|---|
| **Recall** | Retrieve relevant context (chunks ranked by relevance + graph proximity). | Intent-driven: user asks about prior work or the task needs prior context. |
| **Log** | Record event facts + raw materials into an idempotent event-folder upsert. | (a) intent-driven during the task; (b) **PreCompact hook** to dump volatile state. |
| **Distill** | Promote events into the topic graph via R-M-W (read → mutate → write). | (a) intent-driven at task wrap; (b) **SessionEnd hook**; (c) **Stop hook** warns if skipped. |
| **Maintain** | Vault hygiene sweep — lint + decay. | Periodic / user suspects drift. No hook auto-fires. |
The triggers + hook set are **identical** in both plugins. What changes
is which tool / mechanism realizes each phase, and where the LLM-driven
loops actually run.
## Service tier vs Expert tier
| Phase | [`reme-service`](./plugins/reme-service) — work runs **inside reme2** | [`reme-expert`](./plugins/reme-expert) — work runs **outside reme2** |
|---|---|---|
| Recall | `retrieve` MCP tool | `memory_search` / `memory_graph_search` MCP tools, `/reme-recall` slash |
| Log | `remember(mode=log, name=...)` MCP tool | `sync(name=...)` MCP tool (+ `memory_update` / `memory_property_update` for surgical edits) |
| Distill | `remember(mode=distill, ...)` MCP tool → **Ingestor's internal ReActAgent** runs the R-M-W loop inside reme2 | `/reme-distill` slash → **`reme-distiller` subagent** runs the R-M-W loop outside reme2 (own context window) |
| Maintain | `maintain(...)` MCP tool → **Maintainer class** runs the sweep inside reme2 | `/reme-clean` slash → **`reme-curator` subagent** runs the sweep outside reme2 |
Both plugins ship the same hook set: **PreCompact / SessionEnd / Stop**.
The prompt content differs only in which tool / slash command to call.
Recall and Log are thin no-LLM primitives in both modes — only the MCP
names differ.
Both plugins are built on [ReMe2](../reme2) (file_store + watcher + the
three memory services) and the 4-axis Memory schema in
`reme2/memory/schema/`. Both ship a copy of the canonical
`reme2/memory/protocol.md` so the rules (4-axis schema, status state
machine, claim-role confidence, wikilink uniqueness, R-M-W decision
tree) are the same single source of truth across plugin and reme2.
## Pick one
| | `reme-service` | `reme-expert` |
|---|---|---|
| Cognitive load | Low — call three high-level tools and trust the loops | High — agent runs its own R-M-W following the protocol |
| Best when | The agent should focus on its primary task; memory is plumbing | The agent should be a first-class citizen of the vault — every R-M-W decision visible and auditable |
| Subagents | None | `reme-distiller`, `reme-curator` |
| Slash commands | None (the three MCP tools are enough) | `/reme-distill`, `/reme-recall`, `/reme-clean` |
## Install
```text
/plugin marketplace add /Users/huangsen/codes/ReMe/reme-plugin
/plugin install reme-service # OR
/plugin install reme-expert
```
Restart Claude Code so the MCP server / hooks pick up.
Set `VAULT_PATH` so the MCP server points at your vault directory:
```bash
export VAULT_PATH=/path/to/your/vault
```
If unset, falls back to `./vault` relative to launch CWD (the dev default).
## Layout
```
reme-plugin/
├── .claude-plugin/
│ └── marketplace.json # lists both plugins
├── README.md # this file
└── plugins/
├── reme-service/ # Service-tier
│ ├── .claude-plugin/plugin.json
│ ├── .mcp.json # config=reme2/config/service.yaml
│ ├── protocol.md # canonical protocol (copied from reme2/)
│ ├── skills/reme-service/SKILL.md
│ ├── hooks/ # PreCompact / SessionEnd / Stop
│ └── README.md
└── reme-expert/ # Expert-tier
├── .claude-plugin/plugin.json
├── .mcp.json # config=reme2/config/expert.yaml
├── protocol.md # canonical protocol (copied from reme2/)
├── skills/reme-expert/SKILL.md
├── hooks/ # PreCompact / SessionEnd / Stop
├── agents/ # reme-distiller, reme-curator subagents
├── commands/ # /reme-distill, /reme-recall, /reme-clean
└── README.md
```
Each plugin's MCP server is `python -m reme2.mcp.server` with
`PYTHONPATH=${CLAUDE_PLUGIN_ROOT}/../../..` so it resolves the sibling
`reme2/` package without pip install. The two plugins differ only in
which config they pin (`service.yaml` vs `expert.yaml`).
## Switching modes
Both modes write through the same MFS engine and read the same files,
so the vault is portable across modes. To switch:
1. `/plugin uninstall reme-{service|expert}`
2. `/plugin install reme-{expert|service}`
3. Restart Claude Code.
You can keep both installed simultaneously as long as you don't run two
MCP servers at the same vault — the file_store cache will fight.
Switch from **service → expert** when:
- You need fine-grained control over what gets created/edited.
- You want every memory mutation visible in the main session's tool log.
- You want subagents to handle distillation as bounded background work.
Switch from **expert → service** when:
- The agent's main task is what matters; memory should be invisible plumbing.
- You don't want to maintain or read the protocol.
- You're OK with the Ingestor's defaults around topic creation, claim confidence, and wikilink uniqueness.

View file

@ -0,0 +1,9 @@
{
"name": "reme-expert",
"version": "0.1.0",
"description": "Expert-tier markdown vault. Claude Code's agent runs the R-M-W loop directly using raw memory_* MCP primitives, guided by the reme-expert protocol skill. Bundles reme-distiller / reme-curator subagents (auto-spawned at SessionEnd / explicit slash commands), PreCompact + Stop hooks, and /reme-distill /reme-recall /reme-clean commands.",
"author": {
"name": "huangsen"
},
"keywords": ["reme", "vault", "memory", "knowledge-management", "obsidian", "mcp", "expert-tier", "agentic"]
}

View file

@ -0,0 +1,10 @@
{
"reme": {
"command": "python",
"args": ["-m", "reme2.mcp.server", "config=reme2/config/expert.yaml"],
"env": {
"PYTHONUNBUFFERED": "1",
"PYTHONPATH": "${CLAUDE_PLUGIN_ROOT}/../../.."
}
}
}

View file

@ -0,0 +1,130 @@
# reme-expert
Expert-tier markdown vault management for Claude Code. Claude Code's
agent runs the R-M-W loop directly using raw `memory_*` MCP primitives,
guided by the `reme-expert` protocol skill. The LLM-driven Distill loop
and the agentic Maintain sweep run **outside reme2** in dedicated
subagents (`reme-distiller`, `reme-curator`) that own their own context
windows.
For the simpler service-tier alternative where reme2's internal Ingestor
+ Maintainer own those loops, install [reme-service](../reme-service) instead.
## Install
```text
/plugin marketplace add /Users/huangsen/codes/ReMe/reme-plugin
/plugin install reme-expert
```
```bash
export VAULT_PATH=/path/to/your/vault
```
## What's in the box
### MCP server (`reme`)
16 tools from `reme2/config/expert.yaml` — every memory_* primitive plus
`sync` for event-folder upsert:
| Group | Tools | Phase |
|---|---|---|
| Hot-write | `sync` | Log |
| Reads | `memory_search`, `memory_graph_search`, `memory_get`, `memory_list`, `memory_links`, `memory_backlinks`, `memory_resolve_wikilink`, `memory_count_tokens`, `memory_lint` | Recall |
| Writes | `memory_create`, `memory_update`, `memory_property_update`, `memory_rename`, `memory_delete`, `memory_archive` | Log (surgical) / Distill (subagent uses) |
`ingest` is deliberately NOT exposed in this profile — Distill runs in
the `reme-distiller` subagent, not in reme2's internal ReActAgent.
### Skill
`reme-expert` (auto-invoked) — the agent's protocol playbook. Describes
the 4-phase paradigm (Recall / Log / Distill / Maintain), the trigger
table, and which tool / slash command realizes each phase. Symmetrical
with `reme-service`'s skill: same chapter structure, same triggers,
just different tools to call.
### Subagents
| Subagent | Phase | Role |
|---|---|---|
| `reme-distiller` | Distill | Reads active events + materials + linked topics, applies R-M-W decision rules (SKIP / CONTRADICT / EXTEND / CREATE / STATUS-FLIP), returns audit. Auto-spawned by SessionEnd hook; also invokable via `/reme-distill`. |
| `reme-curator` | Maintain | Vault hygiene sweep. Lint + decay; default `dry_run=true`. Invokable via `/reme-clean`. |
Both subagents transclude `../protocol.md` so they see the same rules as
the main agent's skill.
### Slash commands
| Command | Purpose |
|---|---|
| `/reme-distill [hint or paths]` | Spawn `reme-distiller` (auto-fires at SessionEnd; manual invocation available). |
| `/reme-recall <query>` | Explicit deep `memory_graph_search` (8 hits, depth 1). |
| `/reme-clean [target_prefix] [dry_run]` | Spawn `reme-curator` for hygiene sweep. |
### Hooks (parallel to reme-service)
| Event | Action |
|---|---|
| **PreCompact** | Prompt: call `sync(materials=[...])` to dump volatile state into the current thread's event folder, reusing the thread's `name` for upsert; then output a compression guide. |
| **SessionEnd** | Prompt: call `sync` to capture remaining facts, then **invoke `/reme-distill`** to spawn the distiller subagent. |
| **Stop** | `active_events_check.py` — warns via stderr if any `status: active` events remain, suggests `/reme-distill`. |
There are **no SessionStart / UserPromptSubmit auto-recall hooks** — the
agent's skill tells it when to call `memory_search` itself, which is
more accurate than blanket injection. Use `/reme-recall` when you need
deeper recall than your inline search would surface.
## Layout
```
plugins/reme-expert/
├── .claude-plugin/plugin.json
├── .mcp.json # config=reme2/config/expert.yaml
├── protocol.md # canonical protocol; transcluded by SKILL via @../../protocol.md and by subagents via @../protocol.md
├── skills/reme-expert/SKILL.md
├── hooks/
│ ├── hooks.json # PreCompact / SessionEnd / Stop
│ └── active_events_check.py
├── agents/
│ ├── reme-distiller.md
│ └── reme-curator.md
├── commands/
│ ├── reme-distill.md
│ ├── reme-recall.md
│ └── reme-clean.md
└── README.md # this file
```
`.mcp.json` resolves the sibling `reme2/` package via
`PYTHONPATH=${CLAUDE_PLUGIN_ROOT}/../../..` (marketplace root → repo
root). No pip install needed.
`protocol.md` is a copy of the canonical `reme2/memory/protocol.md`. The
SKILL transcludes it from `skills/reme-expert/` via `@../../protocol.md`;
the subagents transclude it from `agents/` via `@../protocol.md`. This
local copy keeps the references stable after marketplace install.
## Workflow at a glance
```
对话过程 → 模型按需调 memory_search / memory_graph_search
/reme-recall 触发更深召回
任务执行中 → sync 持续 upsertsurgical 时 memory_update / memory_property_update
PreCompact (hook) → 提示调 sync dump materials + 输出压缩指引
任务完成 → /reme-distill → reme-distiller 子代理在独立 context 中跑 R-M-W
SessionEnd (hook) → 提示 sync 收尾 + invoke /reme-distill (passing event paths)
Stop (hook) → active_events_check.py 提醒未 distill 的 active events
periodic → /reme-clean → reme-curator 子代理跑 lint + decay
```
## When to switch to service mode
Switch to [reme-service](../reme-service) when:
- You don't want to maintain or read the protocol skill — let the
Ingestor's defaults decide.
- The agent should focus on its primary task and treat memory as plumbing.
- You're OK trusting reme2's auto-decisions on topic creation, claim
confidence, and wikilink uniqueness.

View file

@ -0,0 +1,40 @@
---
name: reme-curator
description: Use proactively for vault hygiene sweeps — finds broken wikilinks, schema violations, stem collisions, and stale events past their freshness window; proposes / applies fixes (rename for collisions, archive for decay, frontmatter patch for schema). Spawned by `/reme-clean` slash command. Owns the Maintainer-style work in its own context window so the main session stays focused. Default behavior is `dry_run` — read the audit first, then re-run with `dry_run=false` to apply.
tools: mcp__reme__memory_list, mcp__reme__memory_get, mcp__reme__memory_links, mcp__reme__memory_backlinks, mcp__reme__memory_resolve_wikilink, mcp__reme__memory_search, mcp__reme__memory_property_update, mcp__reme__memory_update, mcp__reme__memory_rename, mcp__reme__memory_archive, mcp__reme__memory_lint, mcp__reme__memory_count_tokens
model: inherit
---
# reme-curator — vault hygiene subagent
You are the **curator** for the markdown vault. Your job is a single hygiene sweep: scan for issues, propose fixes, and (unless `dry_run=true`) apply them. You run in your own context window so the main session stays focused.
You **MUST** default to `dry_run=true` and report the proposed plan before making any mutation. You **MUST NOT** mutate anything outside the scope of the issues you found (no opportunistic refactors). You **MUST** respect the protocol below — same wikilink-uniqueness gate, same status state machine, same frontmatter axes.
## Working set
The caller (slash command) gives you:
- A scope (target_prefix like `topics/methods/` or empty for the whole vault).
- A switch (`dry_run=true|false`).
- An optional ops filter (`lint`, `decay`, `merge`, `split`, or all). Default ops: `lint` + `decay`.
Your steps:
1. **Scan**`memory_lint` for the cheap diagnostics (broken wikilinks, schema violations, stem collisions). `memory_list` filtered by `status: active` + old `created` for decay candidates.
2. **Categorize** — group findings by op type (rename / archive / property fix / dangling link).
3. **Propose** — write a structured plan: for each finding, the file path, the issue, and the proposed fix. Stop here if `dry_run=true`.
4. **Apply** (only if `dry_run=false`) — execute each proposed fix. Use `memory_rename` for collisions (auto-rewrites incoming wikilinks), `memory_archive` for decay (flips status + moves to Archive/), `memory_property_update` for schema patches.
5. **Return** — short audit: scanned N files, found K issues across [categories], applied L fixes (or "dry_run — no changes").
## Protocol
@../protocol.md
## Anti-patterns
- ❌ Skipping the dry-run report — even on `dry_run=false`, surface the plan first so the caller can read it before fixes land in the audit.
- ❌ Renaming files whose new stem would itself be ambiguous — `memory_rename` refuses; pick a domain-specific qualifier instead.
- ❌ Archiving an `active` event without first `memory_property_update key=status value=distilled` — single-direction state machine.
- ❌ Touching files outside `target_prefix` even if you spot issues there — surface them in the report, but don't fix; out of scope.
- ❌ Using `memory_update` to fix YAML frontmatter — use `memory_property_update`.
- ❌ Bulk-fixing dangling wikilinks by deleting the link text — surface the dangling-link list to the caller; the human or distiller decides whether the target should be created.

View file

@ -0,0 +1,42 @@
---
name: reme-distiller
description: Use proactively for end-of-task or session-end vault distillation — reads active event folders + materials + linked topics, runs the R-M-W loop (Read → Mutate → Write), and flips distilled events' status. Auto-spawned by the SessionEnd hook and by the `/reme-distill` slash command. Owns the cold-path topic update / creation work in its own context window so the main session stays focused on the user's task.
tools: mcp__reme__memory_search, mcp__reme__memory_graph_search, mcp__reme__memory_get, mcp__reme__memory_list, mcp__reme__memory_links, mcp__reme__memory_backlinks, mcp__reme__memory_resolve_wikilink, mcp__reme__memory_create, mcp__reme__memory_update, mcp__reme__memory_property_update, mcp__reme__memory_rename, mcp__reme__memory_archive, mcp__reme__memory_delete, mcp__reme__memory_count_tokens, mcp__reme__sync
model: inherit
---
# reme-distiller — cold-path R-M-W subagent
You are the **distiller** for the markdown vault. Your job is to take a working set (active event folders + their materials + any candidate topics) and integrate it into the long-lived topic graph, following the protocol below. You run in your own context window so the main session can stay focused on the user's task.
You **MUST** apply the R-M-W decision rules in order and stop at the first match. You **MUST NOT** restructure topics while integrating content; minimal edits only. You **MUST** flip the status of every distilled event to `distilled` (active → distilled is the only allowed transition for the events you process).
## Working set
The caller (slash command or SessionEnd hook) gives you:
- A short hint about what the session was about.
- A list of active event folder index paths (or a directive to find them yourself via `memory_list metadata={role: observation, status: active}` filtered to today, or whatever filter the caller supplies).
Your steps:
1. **Discover** — if the caller didn't list event folders, call `memory_list` to find candidates (`status: active`, recent date prefix).
2. **Read** — for each event index, `memory_get` it; follow `## Materials` and read each material whose content you need; resolve linked topics with `memory_resolve_wikilink` and `memory_get` them; pull related context with `memory_search` / `memory_graph_search` if the events name unfamiliar concepts.
3. **Decide per topic** — apply the R-M-W decision rules below. Don't decide blind — finish your reads before you write.
4. **Mutate** — apply the chosen op (`SKIP` / `CONTRADICT``memory_update` / `EXTEND``memory_update` with tail anchor / `CREATE``memory_create`). After every mutation, the watcher updates the graph; you may want to re-`memory_get` if subsequent decisions depend on the new state.
5. **Status flip** — for every event whose content you integrated, `memory_property_update path=<event-index> key=status value=distilled`.
6. **Return** — write a concise audit summary as your final reply: applied ops (with paths), skipped events (with reason), failed ops (with reason). The caller surfaces this back to the main session.
## Protocol
@../protocol.md
## Anti-patterns
- ❌ Creating topics under `events/``sync` owns that path; topics live under `topics/`.
- ❌ Forcing event status straight from `active` to `archived` — single-direction state machine; archive only after `distilled`.
- ❌ Bulk-updating frontmatter via `memory_update` (string substitution on YAML is brittle) — use `memory_property_update`.
- ❌ Restructuring an existing topic body just because you're touching it. Edit the changed sentence; leave the rest.
- ❌ Calling `memory_create` for a topic whose stem `[[X]]` already resolves elsewhere — the create gate refuses; use the `suggested_name` it returns or pick a domain qualifier.
- ❌ Distilling work that's already covered — apply the SKIP rule and move on.
Your reply to the caller is the **only** thing the main session sees from your run. Make it short and concrete.

View file

@ -0,0 +1,30 @@
---
description: Vault hygiene sweep — finds broken wikilinks, schema violations, stem collisions, stale events; proposes / applies fixes via the reme-curator subagent.
argument-hint: [target_prefix] [dry_run=true|false]
---
Spawn the `reme-curator` subagent for a vault hygiene sweep. The subagent runs in its own context window and returns a short audit; the main session stays focused.
Default behavior is `dry_run=true` — report the proposed plan first. Only re-run with `dry_run=false` after the user confirms the plan looks right.
Use the Agent tool with `subagent_type: reme-curator` and the following prompt:
```
Vault hygiene sweep.
Args: $ARGUMENTS
Defaults if not specified in args:
- target_prefix: "" (whole vault)
- dry_run: true
- ops: ["lint", "decay"]
Steps:
1. memory_lint to enumerate broken wikilinks, schema violations, stem collisions.
2. memory_list filtered by status=active + old `created` for decay candidates.
3. Propose a fix plan grouped by op (rename / archive / property fix / dangling).
4. If dry_run=false, apply the plan via memory_rename / memory_archive / memory_property_update.
5. Return: scanned N files, found K issues across [categories], applied L fixes (or "dry_run — no changes").
```
After the subagent returns, surface its audit to the user verbatim. If the plan looks right, suggest re-invoking with `dry_run=false`.

View file

@ -0,0 +1,24 @@
---
description: Distill the current session's active events into the topic graph via the reme-distiller subagent.
argument-hint: [optional event-folder paths or hint]
---
Spawn the `reme-distiller` subagent to handle cold-path R-M-W on the current session's events. The subagent runs in its own context window and returns a short audit summary; the main session stays focused on the user's task.
Use the Agent tool with `subagent_type: reme-distiller` and the following prompt:
```
End-of-task distillation pass.
Hint: $ARGUMENTS
If the hint above doesn't list specific event folder paths, discover them yourself:
- memory_list metadata={role: observation, status: active} filtered to today's date prefix.
- For each candidate, memory_get the index, follow ## Materials, decide whether the content has already been distilled (skip if so).
Apply the R-M-W decision rules (SKIP / CONTRADICT / EXTEND / CREATE / STATUS-FLIP) per the protocol you have loaded. Mutations go through memory_create / memory_update / memory_property_update — every claim-role topic needs `confidence`, every distilled event ends with `status=distilled`.
Return a concise audit: applied ops with paths, skipped events with reason, failed ops with reason.
```
After the subagent returns, surface its audit to the user verbatim — don't paraphrase.

View file

@ -0,0 +1,16 @@
---
description: Deep recall — graph-aware hybrid retrieval on a query. Use when SessionStart-style auto-injection isn't enough.
argument-hint: <query, optionally including [[Anchor]]>
---
Call the `mcp__reme__memory_graph_search` MCP tool with:
```
query: $ARGUMENTS
max_results: 8
graph_depth: 1
```
If the query contains a `[[wikilink]]`, the search will seed BFS at that file. If you want to seed at a specific topic explicitly, edit the call to add `seeds: [<absolute-path>]`.
After the call returns, present the top hits to the user: for each, the file path, the `graph_hop` distance from the seed, and a 1-line excerpt. Don't paraphrase the chunks — they may be load-bearing for the user's question.

View file

@ -0,0 +1,34 @@
#!/usr/bin/env python3
"""Stop hook: warn if there are still events with status: active."""
import os
import re
import sys
from pathlib import Path
vault_path = os.environ.get("VAULT_PATH")
if not vault_path:
sys.exit(0)
events_dir = Path(vault_path) / "events"
if not events_dir.is_dir():
sys.exit(0)
active = 0
fm_re = re.compile(r"^---\s*$(.*?)^---\s*$", re.MULTILINE | re.DOTALL)
status_re = re.compile(r"^status:\s*active\s*$", re.MULTILINE)
for md in events_dir.rglob("*.md"):
try:
text = md.read_text(encoding="utf-8")
except Exception:
continue
fm_match = fm_re.search(text)
if fm_match and status_re.search(fm_match.group(1)):
active += 1
if active > 0:
sys.stderr.write(
f"⚠️ 流程合规:还有 {active} 个 active event 未 distill。"
f"建议调用 `/reme-distill` 把这些 active events 提炼到 topic 后再结束会话。\n",
)

View file

@ -0,0 +1,36 @@
{
"hooks": {
"PreCompact": [
{
"matcher": "auto|manual",
"hooks": [
{
"type": "prompt",
"prompt": "上下文即将被压缩。两步保住关键状态:\n1. 调用 `sync` 把任何尚未落盘的事实写入当前线程的 event folder——一定要在 `materials` 字段里塞进**会丢失的原始素材**(用户原文 prompt、关键工具输出、决策时的中间数据。**复用本任务一直在用的 `name`** 让 sync upsert 到同一文件夹(连续性靠这个保证);只有开启全新逻辑线程时才换名字。零 LLM 成本。\n2. 输出压缩指引:\n\n## 压缩指引\n\n### 当前工作\n[正在做什么,做到哪一步,下一步是什么]\n\n### 关键上下文\n[必须保留的推理链、未完成的分析、重要的中间结论]\n\n### 已持久化\n[本次同步落盘的 event folder 路径——压缩时可省略 raw 细节,需要时 memory_get 索引或 materials 重新读]\n\n### 活跃约束\n[当前需要持续遵守的规则或用户要求]"
}
]
}
],
"SessionEnd": [
{
"hooks": [
{
"type": "prompt",
"prompt": "会话即将结束。两步收尾——**必须**走 distiller subagent不要在主 session 里手动跑 R-M-W\n\n1. 先调用 `sync` 把所有尚未落盘的事实写入对应线程的 event folder`materials` 装原始素材,确定性、零 LLM、复用同名 upsert。\n\n2. 然后调用 `/reme-distill` —— 它派出 `reme-distiller` subagent 在独立 context 中跑 R-M-W读本次会话的 active event folders + materials + 相关 topic按 reme 协议4 轴 schema、claim-role confidence、wikilink 唯一性、path templates、status 状态机)决定哪些 topic update / createflip distilled events 的 status返回 audit 摘要。"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "${VAULT_PYTHON:-python} ${CLAUDE_PLUGIN_ROOT}/hooks/active_events_check.py",
"timeout": 5
}
]
}
]
}
}

View file

@ -0,0 +1,147 @@
# Memory Protocol
Single source of truth for vault schema, conventions, and the R-M-W
write loop. Consumed by:
- **Ingestor's embedded ReAct prompt** (`reme2/memory/ingestor.yaml`
injected as `{protocol}` at load time).
- **Strong-agent SKILL** (`reme-plugin/skills/reme/SKILL.md`
transcluded so the host agent sees the same rules).
Anything that defines schema invariants, path templates, write tool
semantics, or the R-M-W decision tree belongs here. Anything role-
specific (caller framing, audit trail expectations, summary
requirements) stays in the consumer.
## Vault layout
- **Topics** — long-lived cognitive memory at `topics/{folder}/{name}.md`.
A **folder topic** has `folder == name`; it's the cluster's index head.
Short wikilink `[[X]]` resolves to the folder topic if one exists,
else falls back to a unique same-stem file.
- **Events** — fact log of one session at
`events/{YYYY-MM-DD}/{name}/{name}.md`. The `.md` is the **index**
inside a folder; sibling files are **materials** (raw conversation,
tool outputs, data dumps). The index lists them under `## Materials`.
## Frontmatter — 4 schema axes
Every memory declares 4 orthogonal axes. The legacy `category` field is
auto-translated to these axes for back-compat reads, but new writes
should set the axes directly.
| Axis | Values | Meaning |
|---|---|---|
| `lifecycle` | `streaming` / `evolving` / `frozen` | streaming = events (decay/archive); evolving = topics (long-lived, edited); frozen = materials (immutable references) |
| `scope` | `instance` / `class` | instance = a specific moment / object; class = abstract concept / role / pattern |
| `source` | `auto` / `curated` / `derived` | auto = system-captured; curated = human/LLM intent; derived = computed from other memories |
| `role` | `observation` / `claim` / `question` / `profile` / `concept` / `method` / `reference` / `fundamentals` | cognitive role — drives ranking + role-specific validation |
### Conditional fields
- `confidence` ∈ {⏳, ✅, ❌} — **REQUIRED** when `role: claim` (legacy
categories `thesis` / `model`). Same gate applies to `role: question`
(legacy `questions`).
- `status` ∈ {`active`, `distilled`, `archived`} — meaningful only for
`lifecycle: streaming`. Topic-style memories ignore it.
- `originSessionId` — should be set when `source: auto`.
### Standard identity fields
`title`, `description`, `tags`, `created`, `updated`, `topics`,
`parent`. Use today's date for `created` / `updated` on new writes.
## Cross-file references
`[[wikilink]]` syntax. Two forms:
- **Stem form** `[[X]]` — resolved against the file_store's stem index;
prefers the folder topic if one exists.
- **Path form** `[[topics/X/X]]` or `[[topics/X/X.md]]` — anchored at
the vault root.
## Status state machine
`active → distilled → archived` (single direction, no skip). A reverse
or skip transition will be flagged by the Maintainer.
## Wikilink uniqueness
Every create path routes through `MemoryCreate.write`, which refuses to
introduce ambiguity (existing `[[X]]` would resolve to ≥2 paths). When
rejected, the response includes a `suggested_name`. Retry with that, or
pick a domain-specific qualifier (`Apple-Inc` beats `Apple-2`). Never
bypass with `force=true` unless you fully understand the ambiguity.
## Available tools
### Read tools (gather context BEFORE writing)
- `memory_get(path, include_chunks=False)` — full file content +
frontmatter. On an event index, follow `## Materials` and read each
artifact whose content you need.
- `memory_list(path_prefix=None, tags=None, metadata=None, limit=100)`
— list indexed files filtered by prefix / tags / frontmatter.
- `memory_resolve_wikilink(wikilink)` — resolve `[[X]]` to a path;
flags ambiguity / dangling.
- `memory_backlinks(path)` — files linking TO the given path.
- `memory_links(path)` — files the given path links to.
- `memory_search(query, …)` — hybrid (vector + keyword) chunk search.
- `memory_graph_search(query, seeds, graph_depth, …)` — vector +
keyword + graph BFS fusion.
### Write tools (mutations are SSOT-routed; each returns success +
payload + records to audit)
- `memory_update(path, old_string, new_string, replace_all=False)`
body edit by exact-string substitution. Use a tail snippet to append.
- `memory_property_update(path, key, value)` — change one frontmatter
key (`value=null` deletes). Use this to flip status.
- `memory_create(path, metadata, content, overwrite=False, force=False)`
— new file. Reserve for genuinely NEW topics. Do NOT use for events
(`sync` owns events). All paths must be ABSOLUTE under vault_root.
- `memory_rename(old_path, new_path)` — move file + rewrite cross-vault
wikilinks. Refuses on destination conflict or stem ambiguity.
- `memory_delete(path)` — remove a file.
- `memory_archive(path)` — flip `status: archived` and move under
`<vault>/Archive/`.
### Hot-write helper (deterministic, no LLM)
- `sync(name, description?, content?, topics?, tags?, materials?,
on_date?)` — idempotent upsert of an event FOLDER per `(date, name)`.
Reuse the same `name` across calls in one thread to keep extending
the same folder. Refuses on `status: distilled` / `archived` and
returns a `suggested_name`.
## R-M-W decision rules
Apply in order. Stop at the first match.
1. **SKIP** — if material is ALREADY covered by existing topics, reply
with a single line `SKIP: <one-line reason>` and call no tools.
2. **CONTRADICT** — if material CONTRADICTS an existing block, use
`memory_update` with a unique snippet of the outdated text and the
corrected replacement.
3. **EXTEND** — if material EXTENDS an existing topic, use
`memory_update` with a unique TAIL snippet of the existing body, and
`new_string = tail + blank line + new content`.
4. **CREATE** — if material warrants a GENUINELY NEW topic, use
`memory_create` at `topics/{folder}/{name}.md`. Do NOT
`memory_create` under `events/``sync` owns that path.
5. **STATUS FLIP** — after integrating an event's content into a
topic, flip that event's status to `distilled` with
`memory_property_update`.
## Operating principles
- **Read before write.** Always inspect related topics before deciding
CONTRADICT vs EXTEND vs CREATE. The wikilink-uniqueness gate refuses
blind creates; reading first prevents wasted attempts.
- **Minimal edits.** Edit only what must change. Don't restructure
while updating content.
- **Frontmatter on create.** Always include reasonable frontmatter:
the 4 axes, `title`, `created`, `updated`, plus `confidence` when
`role: claim` or `role: question`.
- **Never delete unless asked.** Distillation flips status; it does
not remove events.

View file

@ -0,0 +1,156 @@
---
name: reme-expert
description: Use this skill whenever the user references their personal vault (markdown notes managed by the `reme` MCP, expert-tier surface), or when there's a meaningful session outcome to record / a question that prior work might answer / a need to clean up the vault. Triggers include "what do I know about X", "did I work on Y before", "save this", "记下", "落盘", "提炼", "vault", any mention of topics/events/methodology, or recognizing that a non-trivial session outcome should be recorded. Skill follows a 4-phase paradigm (Recall / Log / Distill / Maintain) projected onto raw `memory_*` MCP primitives + `sync` (event log) + slash commands `/reme-distill` (auto-fired at SessionEnd) / `/reme-recall` / `/reme-clean`. Distill and Maintain run **outside reme2** in dedicated subagents (`reme-distiller`, `reme-curator`) that own their own context windows.
---
# Vault — expert tier
The vault is a personal markdown knowledge base managed by the `reme` MCP server. **Expert tier**: raw `memory_*` primitives + `sync` for event logging, plus slash commands that spawn subagents for the LLM-driven loops. The R-M-W loops for Distill and Maintain run **outside reme2** in subagents you (Claude Code) drive directly via the same primitives.
## Business objects
- **Event** — fact log of one session. A folder `events/{YYYY-MM-DD}/{name}/` containing the index `{name}.md` (Memory schema, `lifecycle: streaming`) plus arbitrary **materials** (raw conversation, tool outputs, data dumps). `status: active → distilled → archived`.
- **Topic** — long-lived cognition. Path `topics/{folder}/{name}.md`. Folder topic = `topics/X/X.md` is the cluster's index head.
Lifecycle: **session → event folder (fact + materials) → distill → topic (cognition)**.
## 4-Phase paradigm
Every interaction with the vault falls into one of four phases. The schema, state machine, and wikilink-uniqueness rules apply to every write you make — see the protocol section below.
### Phase 1: Recall
**What** — retrieve relevant context (chunks ranked by combined relevance + graph proximity).
**Triggers** — intent-driven only: "what do I know about X" / "did I work on Y" / "what's connected to [[Z]]" / task needs prior methodology.
**How (this tier)** — `memory_search(query, ...)` for hybrid vector + keyword; `memory_graph_search(query, seeds?, graph_depth?, ...)` when you want context expansion through wikilinks. For deeper recall (8 hits, depth 1) invoke `/reme-recall <query>`.
**Where the work runs** — `reme2` thin primitive (FileGraph + FTS + vector store). No LLM.
```
memory_search query="apple valuation" max_results=5
memory_graph_search query="see [[Apple]]" graph_depth=1
/reme-recall apple valuation around [[Apple]]
```
Each result chunk carries `graph_hop` so you know how far it sits from your seeds.
Use `memory_get` / `memory_list` / `memory_links` / `memory_backlinks` / `memory_resolve_wikilink` for primary-key reads when you already know the path or want to follow a specific edge.
### Phase 2: Log
**What** — record event facts + raw materials. Idempotent upsert: same `name` continues the same event folder.
**Triggers** — (a) intent-driven: a meaningful fact / output / decision just landed during the task; (b) **PreCompact hook**: prompt fires asking you to dump volatile state into `materials` before context truncation.
**How (this tier)** — `sync(name=..., content?, materials?, topics?, tags?, on_date?, description?)`.
**Where the work runs** — `reme2` thin primitive (sync upsert). No LLM.
**Continuity rule**: pick a stable `name` per logical thread and reuse it across calls. Each call extends the same folder.
```
# First call in the thread
sync
name: <kebab-case, stable across this thread>
description: <one line> # set on first call only
content: |
## ops
- did X with tool Y
## findings
- Z turned out to be ...
topics: ["[[X]]", "[[Y]]"]
tags: [...]
materials:
- filename: raw-prompt.md
content: <user's original prompt>
- filename: tool-output.txt
content: <verbose output worth preserving raw>
# Later in the SAME thread (or PreCompact firing)
sync
name: <same name>
content: |
## follow-up
- second-pass facts
materials:
- filename: tool-output.txt # collision → auto-suffixed to tool-output-2.txt
content: <new output>
```
Each subsequent call appends `## Update — {iso}` to the index body, lands new materials as siblings, unions topics/tags. Refuses if the event has already been distilled — pick a fresh `name` from `suggested_name`.
**Inline surgical edits during the task** (not Distill — Distill is a bigger loop):
- Change one frontmatter field on an existing topic → `memory_property_update path=... key=... value=...`
- Edit one body snippet on an existing topic → `memory_update path=... old_string=... new_string=...`
- These are still Phase 2 (you're recording a small fact); use them when the change is small and obvious. For multi-step R-M-W, defer to Phase 3.
### Phase 3: Distill
**What** — promote active events into the topic graph: read events + materials + linked topics, decide which existing topics to update vs. which deserve a brand-new topic, flip distilled events' status.
**Triggers** — (a) intent-driven: task wraps and the working set is ready; (b) **SessionEnd hook**: prompt fires asking you to invoke `/reme-distill`; (c) **Stop hook**: stderr warning if any active events remain undistilled.
**How (this tier)** — invoke `/reme-distill [hint or event paths]`. The slash command spawns the `reme-distiller` subagent; it reads the protocol, runs the R-M-W loop using raw `memory_*` tools, returns an audit summary.
**Where the work runs** — **outside reme2**. The `reme-distiller` subagent is a Claude Code agent with its own context window — main session doesn't bloat with the 5-10 tool-call R-M-W sequence. The subagent uses the same `memory_*` primitives this skill describes; it just runs them in isolation and reports back.
**Cold-path rule**: handoff once at task wrap, not per turn. The SessionEnd hook auto-fires the prompt — your job there is just to (1) `sync` final state and (2) invoke `/reme-distill`, passing event paths from this session if you have them.
```
/reme-distill end-of-task distillation. event folders this session:
events/2026-05-09/<name1>/<name1>.md
events/2026-05-09/<name2>/<name2>.md
```
The subagent owns the decision rules (SKIP / CONTRADICT / EXTEND / CREATE / STATUS-FLIP) and the schema enforcement (claim-role confidence, wikilink uniqueness, path templates). Surface its audit verbatim — it's the source of truth for what changed.
### Phase 4: Maintain
**What** — vault hygiene sweep. Lint (broken wikilinks, schema violations, stem collisions) + decay (move stale events to archive).
**Triggers** — periodic / user suspects vault drift. No hook auto-fires.
**How (this tier)** — invoke `/reme-clean [target_prefix] [dry_run]`. The slash command spawns the `reme-curator` subagent; it runs `memory_lint` for diagnostics + uses `memory_rename` / `memory_archive` / `memory_property_update` to apply fixes. Default `dry_run=true`.
**Where the work runs** — **outside reme2**. `memory_lint` is a thin reme2 projection of the Maintainer's diagnostics, but the *application* of fixes is agentic — the curator decides which renames / archives to actually apply and uses raw write primitives, all in the subagent's own context.
```
/reme-clean # whole vault, dry_run=true
/reme-clean events/2026-04 dry_run=false # apply within a path subset
```
Always read the dry-run audit first; re-run with `dry_run=false` if the proposals look right.
For a quick read-only diagnostic without spawning a subagent, you can call `memory_lint` directly — but apply-side fixes still go through `/reme-clean`.
## Trigger → Phase quick reference
| Trigger | Phase | What you do |
|---|---|---|
| User asks about prior work / [[X]] | Recall | `memory_search` / `memory_graph_search` / `/reme-recall` |
| Fact lands during task | Log | `sync(name=...)` |
| Small surgical edit needed | Log | `memory_update` / `memory_property_update` inline |
| **PreCompact hook** fires | Log (urgent dump) | `sync(name=..., materials=[...])` + compression guide |
| Task wraps | Distill | `/reme-distill` |
| **SessionEnd hook** fires | Log + Distill | final `sync` then `/reme-distill <event paths>` |
| **Stop hook** warns active events | Distill (compliance) | `/reme-distill` for the listed events |
| User suspects vault drift / periodic | Maintain | `/reme-clean` (dry_run=true) then `dry_run=false` |
## Protocol (the rules every write must respect)
These apply to every `memory_create` / `memory_update` / `memory_property_update` / `sync` you make and to every R-M-W decision the distiller subagent runs.
@../../protocol.md
## Anti-patterns
- ❌ Picking a fresh `name` on every `sync` call within the same logical thread → fragments. **Reuse the same name.**
- ❌ Reusing the same `sync` `name` across genuinely unrelated threads on the same day → silent merge.
- ❌ Running R-M-W inline in the main session at end-of-task — that's what `/reme-distill` exists for. Use it so the loop runs in the subagent's own context.
- ❌ Using `memory_update` to edit YAML frontmatter — use `memory_property_update`.
- ❌ Calling `memory_create` for a topic whose stem already resolves to another path — wikilink-uniqueness gate refuses; rename or pick a domain-specific qualifier.
- ❌ Writing a thesis/model/questions topic without `confidence` — schema validation refuses; either add it before creating, or let the distiller surface the gap.
- ❌ Writing event files manually with `memory_create` under `events/` — use `sync` so path template + Materials footer + idempotent upsert all run.
- ❌ Forcing event status straight from `active` to `archived` — single-direction state machine; archive only after `distilled`.
- ❌ Skipping `dry_run=true` on `/reme-clean` — read the plan first.
## What's parallel to service mode (for comparison)
| Phase | Service tier (work runs **inside reme2**) | Expert tier (work runs **outside reme2**) |
|---|---|---|
| Recall | `retrieve` | `memory_search` / `memory_graph_search` / `/reme-recall` |
| Log | `remember(mode=log, name=...)` | `sync(name=...)` |
| Distill | `remember(mode=distill, ...)` (Ingestor's internal ReActAgent) | `/reme-distill` (Claude Code subagent) |
| Maintain | `maintain(...)` (Maintainer class) | `/reme-clean` (Claude Code subagent) |
The 4 phases and their triggers are identical across modes; only the locus of computation for Distill and Maintain differs (and Recall/Log have different MCP names).

View file

@ -0,0 +1,9 @@
{
"name": "reme-service",
"version": "0.1.0",
"description": "Service-tier markdown vault MCP. Three high-level tools (retrieve / remember / maintain). reme2's internal Ingestor + Maintainer own the R-M-W loop; the agent just hands off material.",
"author": {
"name": "huangsen"
},
"keywords": ["reme", "vault", "memory", "knowledge-management", "mcp", "service-tier"]
}

View file

@ -0,0 +1,10 @@
{
"reme": {
"command": "python",
"args": ["-m", "reme2.mcp.server", "config=reme2/config/service.yaml"],
"env": {
"PYTHONUNBUFFERED": "1",
"PYTHONPATH": "${CLAUDE_PLUGIN_ROOT}/../../.."
}
}
}

View file

@ -0,0 +1,102 @@
# reme-service
Service-tier markdown vault management for Claude Code. Three high-level
MCP tools — `retrieve` / `remember` / `maintain` — backed by reme2's
internal Ingestor + Maintainer. The agent calls the tools with intent;
the LLM-driven R-M-W loop (Distill) and the sweep loop (Maintain) run
**inside reme2**.
For the agentic alternative where Claude Code's own subagents run those
loops outside reme2, install [reme-expert](../reme-expert) instead.
## Install
```text
/plugin marketplace add /Users/huangsen/codes/ReMe/reme-plugin
/plugin install reme-service
```
```bash
export VAULT_PATH=/path/to/your/vault
```
## What's in the box
### MCP server (`reme`)
Three tools projected from the three memory services
(`reme2/config/service.yaml`):
| Tool | Phase | Backend |
|---|---|---|
| `retrieve(query, max_results?, graph_depth?, seeds?, ...)` | Recall | thin (memory_graph_search) |
| `remember(mode=log\|distill, name?, content, materials?, related_paths?, ...)` | Log / Distill | sync upsert (mode=log, no LLM) / Ingestor's internal ReActAgent (mode=distill) |
| `maintain(target_prefix?, ops?, dry_run?, decay_days?)` | Maintain | Maintainer class |
### Skill
`reme-service` (auto-invoked) — describes the 4-phase paradigm
(Recall / Log / Distill / Maintain) and how each phase maps to the three
tools. Symmetrical with `reme-expert`'s skill: same chapter structure,
same triggers, just different tools to call.
### Hooks (parallel to reme-expert)
| Event | Action |
|---|---|
| **PreCompact** | Prompt: call `remember(mode=log, materials=[...])` to dump volatile state into the current thread's event folder, reusing the thread's `name` for upsert; then output a compression guide. |
| **SessionEnd** | Prompt: call `remember(mode=log)` to capture remaining facts, then call `remember(mode=distill, related_paths=[...])` once to hand off the working set to the Ingestor. |
| **Stop** | `active_events_check.py` — warns via stderr if any `status: active` events remain, suggests `remember(mode=distill)`. |
There are **no SessionStart / UserPromptSubmit auto-recall hooks** — the
agent's skill tells it when to call `retrieve` itself, which is more
accurate than blanket injection.
## Layout
```
plugins/reme-service/
├── .claude-plugin/plugin.json
├── .mcp.json # config=reme2/config/service.yaml
├── protocol.md # canonical protocol, transcluded by SKILL via @../../protocol.md
├── skills/reme-service/SKILL.md
├── hooks/
│ ├── hooks.json # PreCompact / SessionEnd / Stop
│ └── active_events_check.py
└── README.md # this file
```
`.mcp.json` resolves the sibling `reme2/` package via
`PYTHONPATH=${CLAUDE_PLUGIN_ROOT}/../../..` (marketplace root → repo
root). No pip install needed.
`protocol.md` is a copy of the canonical `reme2/memory/protocol.md`. The
same file is read by reme2's internal Ingestor (injected as `{protocol}`
into the ReActAgent's sys_prompt) — this plugin ships its own copy so
the SKILL's `@../../protocol.md` transclusion works after marketplace
install.
## Workflow at a glance
```
对话过程 → 模型按需调 retrieveintent-driven
任务执行中 → remember(mode=log) 持续 upsertmaterials 装原始素材(确定性、零 LLM
PreCompact (hook) → 提示调 remember(mode=log) dump materials + 输出压缩指引
任务完成 → remember(mode=distill) 一次 → Ingestor 内部 ReActAgent 跑 R-M-W → 返回 audit
SessionEnd (hook) → 提示 remember(mode=log) 收尾 + remember(mode=distill) handoff
Stop (hook) → active_events_check.py 提醒未 distill 的 active events
periodic → maintain(dry_run=true) 看一眼,必要时 dry_run=false 应用
```
## When to switch to expert mode
The service plugin hides the schema, claim confidence, wikilink
uniqueness, path templates, and status state machine — `remember(mode=
distill)` enforces them all underneath. Switch to
[reme-expert](../reme-expert) when:
- You need fine-grained control over what gets created/edited.
- You want every memory mutation visible in the main session's tool log
(vs hidden inside the Ingestor's internal ReActAgent).
- You want subagents to handle distillation as bounded background work
with their own context windows.

View file

@ -0,0 +1,35 @@
#!/usr/bin/env python3
"""Stop hook: warn if there are still events with status: active."""
import os
import re
import sys
from pathlib import Path
vault_path = os.environ.get("VAULT_PATH")
if not vault_path:
sys.exit(0)
events_dir = Path(vault_path) / "events"
if not events_dir.is_dir():
sys.exit(0)
active = 0
fm_re = re.compile(r"^---\s*$(.*?)^---\s*$", re.MULTILINE | re.DOTALL)
status_re = re.compile(r"^status:\s*active\s*$", re.MULTILINE)
for md in events_dir.rglob("*.md"):
try:
text = md.read_text(encoding="utf-8")
except Exception:
continue
fm_match = fm_re.search(text)
if fm_match and status_re.search(fm_match.group(1)):
active += 1
if active > 0:
sys.stderr.write(
f"⚠️ 流程合规:还有 {active} 个 active event 未 distill。"
f"建议调用 `remember(mode=distill, content=..., related_paths=[...])` "
f"把这些 active events 提炼到 topic 后再结束会话。\n",
)

View file

@ -0,0 +1,36 @@
{
"hooks": {
"PreCompact": [
{
"matcher": "auto|manual",
"hooks": [
{
"type": "prompt",
"prompt": "上下文即将被压缩。两步保住关键状态:\n1. 调用 `remember(mode=log, name=..., content=..., materials=[...])` 把任何尚未落盘的事实写入当前线程的 event folder——一定要在 `materials` 字段里塞进**会丢失的原始素材**(用户原文 prompt、关键工具输出、决策时的中间数据。**复用本任务一直在用的 `name`** 让 remember 在同一文件夹 upsert连续性靠这个保证只有开启全新逻辑线程时才换名字。零 LLM 成本。\n2. 输出压缩指引:\n\n## 压缩指引\n\n### 当前工作\n[正在做什么,做到哪一步,下一步是什么]\n\n### 关键上下文\n[必须保留的推理链、未完成的分析、重要的中间结论]\n\n### 已持久化\n[本次落盘的 event folder 路径——压缩时可省略 raw 细节,需要时再 retrieve / memory_get 索引或 materials 重新读]\n\n### 活跃约束\n[当前需要持续遵守的规则或用户要求]"
}
]
}
],
"SessionEnd": [
{
"hooks": [
{
"type": "prompt",
"prompt": "会话即将结束。两步收尾:\n\n1. 先调用 `remember(mode=log, name=..., materials=[...])` 把所有尚未落盘的事实写入对应线程的 event folder`materials` 装原始素材,确定性、零 LLM、复用同名 upsert。\n\n2. 然后调用 `remember(mode=distill, content=..., related_paths=[...])` —— **reme2 内部的 Ingestor 会跑 R-M-W 循环**:读本次会话的 active event folders + materials + 相关 topic按 reme 协议4 轴 schema、claim-role confidence、wikilink 唯一性、path templates、status 状态机)决定哪些 topic update / createflip distilled events 的 status。在 `content` 里写一两行 session 摘要 + 提示,在 `related_paths` 里列本次 sync 过的 event folder 索引路径。\n\nDistill 是 cold-pathhandoff 一次就够了,不要每轮调。"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "${VAULT_PYTHON:-python} ${CLAUDE_PLUGIN_ROOT}/hooks/active_events_check.py",
"timeout": 5
}
]
}
]
}
}

View file

@ -0,0 +1,147 @@
# Memory Protocol
Single source of truth for vault schema, conventions, and the R-M-W
write loop. Consumed by:
- **Ingestor's embedded ReAct prompt** (`reme2/memory/ingestor.yaml`
injected as `{protocol}` at load time).
- **Strong-agent SKILL** (`reme-plugin/skills/reme/SKILL.md`
transcluded so the host agent sees the same rules).
Anything that defines schema invariants, path templates, write tool
semantics, or the R-M-W decision tree belongs here. Anything role-
specific (caller framing, audit trail expectations, summary
requirements) stays in the consumer.
## Vault layout
- **Topics** — long-lived cognitive memory at `topics/{folder}/{name}.md`.
A **folder topic** has `folder == name`; it's the cluster's index head.
Short wikilink `[[X]]` resolves to the folder topic if one exists,
else falls back to a unique same-stem file.
- **Events** — fact log of one session at
`events/{YYYY-MM-DD}/{name}/{name}.md`. The `.md` is the **index**
inside a folder; sibling files are **materials** (raw conversation,
tool outputs, data dumps). The index lists them under `## Materials`.
## Frontmatter — 4 schema axes
Every memory declares 4 orthogonal axes. The legacy `category` field is
auto-translated to these axes for back-compat reads, but new writes
should set the axes directly.
| Axis | Values | Meaning |
|---|---|---|
| `lifecycle` | `streaming` / `evolving` / `frozen` | streaming = events (decay/archive); evolving = topics (long-lived, edited); frozen = materials (immutable references) |
| `scope` | `instance` / `class` | instance = a specific moment / object; class = abstract concept / role / pattern |
| `source` | `auto` / `curated` / `derived` | auto = system-captured; curated = human/LLM intent; derived = computed from other memories |
| `role` | `observation` / `claim` / `question` / `profile` / `concept` / `method` / `reference` / `fundamentals` | cognitive role — drives ranking + role-specific validation |
### Conditional fields
- `confidence` ∈ {⏳, ✅, ❌} — **REQUIRED** when `role: claim` (legacy
categories `thesis` / `model`). Same gate applies to `role: question`
(legacy `questions`).
- `status` ∈ {`active`, `distilled`, `archived`} — meaningful only for
`lifecycle: streaming`. Topic-style memories ignore it.
- `originSessionId` — should be set when `source: auto`.
### Standard identity fields
`title`, `description`, `tags`, `created`, `updated`, `topics`,
`parent`. Use today's date for `created` / `updated` on new writes.
## Cross-file references
`[[wikilink]]` syntax. Two forms:
- **Stem form** `[[X]]` — resolved against the file_store's stem index;
prefers the folder topic if one exists.
- **Path form** `[[topics/X/X]]` or `[[topics/X/X.md]]` — anchored at
the vault root.
## Status state machine
`active → distilled → archived` (single direction, no skip). A reverse
or skip transition will be flagged by the Maintainer.
## Wikilink uniqueness
Every create path routes through `MemoryCreate.write`, which refuses to
introduce ambiguity (existing `[[X]]` would resolve to ≥2 paths). When
rejected, the response includes a `suggested_name`. Retry with that, or
pick a domain-specific qualifier (`Apple-Inc` beats `Apple-2`). Never
bypass with `force=true` unless you fully understand the ambiguity.
## Available tools
### Read tools (gather context BEFORE writing)
- `memory_get(path, include_chunks=False)` — full file content +
frontmatter. On an event index, follow `## Materials` and read each
artifact whose content you need.
- `memory_list(path_prefix=None, tags=None, metadata=None, limit=100)`
— list indexed files filtered by prefix / tags / frontmatter.
- `memory_resolve_wikilink(wikilink)` — resolve `[[X]]` to a path;
flags ambiguity / dangling.
- `memory_backlinks(path)` — files linking TO the given path.
- `memory_links(path)` — files the given path links to.
- `memory_search(query, …)` — hybrid (vector + keyword) chunk search.
- `memory_graph_search(query, seeds, graph_depth, …)` — vector +
keyword + graph BFS fusion.
### Write tools (mutations are SSOT-routed; each returns success +
payload + records to audit)
- `memory_update(path, old_string, new_string, replace_all=False)`
body edit by exact-string substitution. Use a tail snippet to append.
- `memory_property_update(path, key, value)` — change one frontmatter
key (`value=null` deletes). Use this to flip status.
- `memory_create(path, metadata, content, overwrite=False, force=False)`
— new file. Reserve for genuinely NEW topics. Do NOT use for events
(`sync` owns events). All paths must be ABSOLUTE under vault_root.
- `memory_rename(old_path, new_path)` — move file + rewrite cross-vault
wikilinks. Refuses on destination conflict or stem ambiguity.
- `memory_delete(path)` — remove a file.
- `memory_archive(path)` — flip `status: archived` and move under
`<vault>/Archive/`.
### Hot-write helper (deterministic, no LLM)
- `sync(name, description?, content?, topics?, tags?, materials?,
on_date?)` — idempotent upsert of an event FOLDER per `(date, name)`.
Reuse the same `name` across calls in one thread to keep extending
the same folder. Refuses on `status: distilled` / `archived` and
returns a `suggested_name`.
## R-M-W decision rules
Apply in order. Stop at the first match.
1. **SKIP** — if material is ALREADY covered by existing topics, reply
with a single line `SKIP: <one-line reason>` and call no tools.
2. **CONTRADICT** — if material CONTRADICTS an existing block, use
`memory_update` with a unique snippet of the outdated text and the
corrected replacement.
3. **EXTEND** — if material EXTENDS an existing topic, use
`memory_update` with a unique TAIL snippet of the existing body, and
`new_string = tail + blank line + new content`.
4. **CREATE** — if material warrants a GENUINELY NEW topic, use
`memory_create` at `topics/{folder}/{name}.md`. Do NOT
`memory_create` under `events/``sync` owns that path.
5. **STATUS FLIP** — after integrating an event's content into a
topic, flip that event's status to `distilled` with
`memory_property_update`.
## Operating principles
- **Read before write.** Always inspect related topics before deciding
CONTRADICT vs EXTEND vs CREATE. The wikilink-uniqueness gate refuses
blind creates; reading first prevents wasted attempts.
- **Minimal edits.** Edit only what must change. Don't restructure
while updating content.
- **Frontmatter on create.** Always include reasonable frontmatter:
the 4 axes, `title`, `created`, `updated`, plus `confidence` when
`role: claim` or `role: question`.
- **Never delete unless asked.** Distillation flips status; it does
not remove events.

View file

@ -0,0 +1,154 @@
---
name: reme-service
description: Use this skill whenever the user references their personal vault (markdown notes managed by the `reme` MCP, service-tier surface), or when there's a meaningful session outcome to record / a question that prior work might answer / a need to clean up the vault. Triggers include "what do I know about X", "did I work on Y before", "save this", "记下", "落盘", "提炼", "vault", any mention of topics/events/methodology, or recognizing that a non-trivial session outcome should be recorded. Skill follows a 4-phase paradigm (Recall / Log / Distill / Maintain) projected onto three MCP tools — `retrieve`, `remember(mode=log|distill)`, `maintain`. The R-M-W loop for Distill and the sweep for Maintain run **inside reme2** (the Ingestor's internal ReActAgent and the Maintainer class respectively).
---
# Vault — service tier
The vault is a personal markdown knowledge base managed by the `reme` MCP server. **Service tier**: three high-level MCP tools, one per memory service. The R-M-W loops for Distill and Maintain run **inside reme2** — you hand off material with the right intent and the Ingestor / Maintainer do the work.
## Business objects
- **Event** — fact log of one session. A folder `events/{YYYY-MM-DD}/{name}/` containing the index `{name}.md` (Memory schema, `lifecycle: streaming`) plus arbitrary **materials** (raw conversation, tool outputs, data dumps). `status: active → distilled → archived`.
- **Topic** — long-lived cognition. Path `topics/{folder}/{name}.md`. Folder topic = `topics/X/X.md` is the cluster's index head.
Lifecycle: **session → event folder (fact + materials) → distill → topic (cognition)**.
## 4-Phase paradigm
Every interaction with the vault falls into one of four phases. The schema, state machine, and wikilink-uniqueness rules are enforced by reme2 underneath — you don't manage them.
### Phase 1: Recall
**What** — retrieve relevant context (chunks ranked by combined relevance + graph proximity).
**Triggers** — intent-driven only: "what do I know about X" / "did I work on Y" / "what's connected to [[Z]]" / task needs prior methodology.
**How (this tier)** — `retrieve(query, max_results?, graph_depth?, seeds?, ...)`. Anchor mode: include `[[Target]]` in the query to seed BFS at that file. Topic-rooted mode: pass `seeds` explicitly.
**Where the work runs** — `reme2` thin primitive (memory_graph_search backend). No LLM.
```
retrieve query="apple valuation" max_results=5
retrieve query="see [[Apple]]" graph_depth=1 # anchored
retrieve query="any methodology" seeds=["topics/methods/dcf.md"] # topic-rooted
```
Each result chunk carries `graph_hop` so you know how far it sits from your seeds.
### Phase 2: Log
**What** — record event facts + raw materials. Idempotent upsert: same `name` continues the same event folder.
**Triggers** — (a) intent-driven: a meaningful fact / output / decision just landed during the task; (b) **PreCompact hook**: prompt fires asking you to dump volatile state into `materials` before context truncation.
**How (this tier)** — `remember(mode=log, name=..., content?, materials?, topics?, tags?, on_date?)`.
**Where the work runs** — `reme2` thin primitive (sync upsert). No LLM.
**Continuity rule**: pick a stable `name` per logical thread and reuse it across calls. Each call extends the same folder.
```
# First call in the thread
remember
mode: log
name: <kebab-case, stable across this thread>
description: <one line> # set on first call only
content: |
## ops
- did X with tool Y
## findings
- Z turned out to be ...
topics: ["[[X]]", "[[Y]]"]
tags: [...]
materials:
- filename: raw-prompt.md
content: <user's original prompt>
- filename: tool-output.txt
content: <verbose output worth preserving raw>
# Later in the SAME thread (or PreCompact firing)
remember
mode: log
name: <same name>
content: |
## follow-up
- second-pass facts
materials:
- filename: tool-output.txt # collision → auto-suffixed to tool-output-2.txt
content: <new output>
```
Each subsequent call appends `## Update — {iso}` to the index body, lands new materials as siblings, unions topics/tags. Refuses if the event has already been distilled — pick a fresh `name` from `suggested_name`.
### Phase 3: Distill
**What** — promote active events into the topic graph: read events + materials + linked topics, decide which existing topics to update vs. which deserve a brand-new topic, flip distilled events' status.
**Triggers** — (a) intent-driven: task wraps and the working set is ready; (b) **SessionEnd hook**: prompt fires asking you to call `remember(mode=distill)` once; (c) **Stop hook**: stderr warning if any active events remain undistilled.
**How (this tier)** — `remember(mode=distill, content, hint?, target_path?, metadata?, related_paths?)`. Default `mode` is `distill`, so the parameter can be omitted.
**Where the work runs** — **inside reme2**. The Ingestor's internal ReActAgent reads the working set + linked topics, applies the R-M-W decision rules (SKIP / CONTRADICT / EXTEND / CREATE / STATUS-FLIP), enforces claim-role confidence + wikilink uniqueness + path templates underneath, returns an audit.
**Cold-path rule**: handoff once at task wrap, not per turn. Hand off the working set in two interchangeable forms — `content` (inline material — hint, summary, or raw text) and/or `related_paths` (event folder indexes; the Ingestor follows `## Materials` to read each artifact).
```
remember
mode: distill # default — can be omitted
content: |
Distill these active events into the topic graph. Update existing
topics where extended; create new topics only for genuinely new
cognitive nodes. Flip each distilled event's status.
Session summary: <2-3 line recap>
hint: "End-of-task distillation — minimal edits."
related_paths:
- <abs path of event folder 1's index .md>
- <abs path of event folder 2's index .md>
```
The Ingestor enforces the schema underneath: claim-role topics need `confidence` ∈ {⏳ ✅ ❌}, wikilink uniqueness, path templates. You don't drive any of that — describe intent in `content` / `hint`.
### Phase 4: Maintain
**What** — vault hygiene sweep. Lint (broken wikilinks, schema violations, stem collisions) + decay (move stale events to archive).
**Triggers** — periodic / user suspects vault drift. No hook auto-fires.
**How (this tier)** — `maintain(target_prefix?, ops?, dry_run?, decay_days?)`. Default `dry_run=true` and `ops=["lint","decay"]`.
**Where the work runs** — **inside reme2**. The Maintainer class scans, proposes ops, resolves conflicts, applies (only when `dry_run=false`), returns an audit.
```
maintain # full vault, lint + decay, dry_run=true
maintain target_prefix="events/2026-04" dry_run=false # apply within a path subset
maintain ops=["lint"] # diagnostics only
```
Always read the dry-run audit first; re-run with `dry_run=false` if the proposals look right.
## Trigger → Phase quick reference
| Trigger | Phase | What you do |
|---|---|---|
| User asks about prior work / [[X]] | Recall | `retrieve` |
| Fact lands during task | Log | `remember(mode=log, name=...)` |
| **PreCompact hook** fires | Log (urgent dump) | `remember(mode=log, name=..., materials=[...])` + compression guide |
| Task wraps | Distill | `remember(mode=distill, related_paths=[...])` |
| **SessionEnd hook** fires | Log + Distill | final `remember(mode=log)` then `remember(mode=distill)` |
| **Stop hook** warns active events | Distill (compliance) | `remember(mode=distill)` for the listed events |
| User suspects vault drift / periodic | Maintain | `maintain(dry_run=true)` then `dry_run=false` |
## Protocol (the rules reme2 enforces underneath)
Don't manage these — but knowing they exist explains why a write might be refused or a `suggested_name` returned.
@../../protocol.md
## Anti-patterns
- ❌ Picking a fresh `name` on every `remember(mode=log)` call within the same logical thread → fragments the thread. **Reuse the same name.**
- ❌ Reusing the same `name` across genuinely unrelated threads on the same day → silent merge.
- ❌ Calling `remember(mode=distill)` per turn → it's a handoff tool, not a per-turn tool. Once at end-of-task is the rule.
- ❌ Skipping `dry_run=true` on `maintain` for a fresh vault — read what it would do first.
- ❌ Trying to drive schema decisions from your side (claim confidence, path templates, status state machine) — the Ingestor enforces them; pass intent via `content` / `hint` and let it ask if it needs more.
- ❌ Trying to do R-M-W manually with raw `memory_*` tools — those aren't exposed in this tier. Switch to **expert** mode if you need direct primitives.
## What you DON'T have to think about
- The schema (4 axes: lifecycle / scope / source / role) — `remember(mode=distill)` enforces it.
- Wikilink uniqueness — the create gate refuses ambiguity; the Ingestor handles `suggested_name` retries.
- Path templates (`topics/{X}/{X}.md`, `events/{date}/{name}/...`) — the Ingestor / `remember(mode=log)` apply them.
- Status state machine — `remember(mode=distill)` flips it; `maintain` decays.
- Claim-role confidence — the Ingestor refuses claim-role topics without it; pass intent and let it ask.
If you need any of those decisions surfaced (e.g. "I want to manually curate this topic"), switch to **expert** mode which gives you the raw `memory_*` primitives.