diff --git a/.gitignore b/.gitignore index 81830405..264f523c 100644 --- a/.gitignore +++ b/.gitignore @@ -44,5 +44,3 @@ meta_memory/* memories/* .reme/* /vault -/reme-plugin -/reme2/vault \ No newline at end of file diff --git a/reme-plugin/.claude-plugin/marketplace.json b/reme-plugin/.claude-plugin/marketplace.json new file mode 100644 index 00000000..dafdc173 --- /dev/null +++ b/reme-plugin/.claude-plugin/marketplace.json @@ -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" + } + ] +} diff --git a/reme-plugin/README.md b/reme-plugin/README.md new file mode 100644 index 00000000..c65e1b12 --- /dev/null +++ b/reme-plugin/README.md @@ -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. diff --git a/reme-plugin/plugins/reme-expert/.claude-plugin/plugin.json b/reme-plugin/plugins/reme-expert/.claude-plugin/plugin.json new file mode 100644 index 00000000..aec48f1f --- /dev/null +++ b/reme-plugin/plugins/reme-expert/.claude-plugin/plugin.json @@ -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"] +} diff --git a/reme-plugin/plugins/reme-expert/.mcp.json b/reme-plugin/plugins/reme-expert/.mcp.json new file mode 100644 index 00000000..b4ca0cb4 --- /dev/null +++ b/reme-plugin/plugins/reme-expert/.mcp.json @@ -0,0 +1,10 @@ +{ + "reme": { + "command": "python", + "args": ["-m", "reme2.mcp.server", "config=reme2/config/expert.yaml"], + "env": { + "PYTHONUNBUFFERED": "1", + "PYTHONPATH": "${CLAUDE_PLUGIN_ROOT}/../../.." + } + } +} diff --git a/reme-plugin/plugins/reme-expert/README.md b/reme-plugin/plugins/reme-expert/README.md new file mode 100644 index 00000000..3e93ba61 --- /dev/null +++ b/reme-plugin/plugins/reme-expert/README.md @@ -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 ` | 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 持续 upsert;surgical 时 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. diff --git a/reme-plugin/plugins/reme-expert/agents/reme-curator.md b/reme-plugin/plugins/reme-expert/agents/reme-curator.md new file mode 100644 index 00000000..3ae58555 --- /dev/null +++ b/reme-plugin/plugins/reme-expert/agents/reme-curator.md @@ -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. diff --git a/reme-plugin/plugins/reme-expert/agents/reme-distiller.md b/reme-plugin/plugins/reme-expert/agents/reme-distiller.md new file mode 100644 index 00000000..2d33142d --- /dev/null +++ b/reme-plugin/plugins/reme-expert/agents/reme-distiller.md @@ -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= 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. diff --git a/reme-plugin/plugins/reme-expert/commands/reme-clean.md b/reme-plugin/plugins/reme-expert/commands/reme-clean.md new file mode 100644 index 00000000..146a5376 --- /dev/null +++ b/reme-plugin/plugins/reme-expert/commands/reme-clean.md @@ -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`. diff --git a/reme-plugin/plugins/reme-expert/commands/reme-distill.md b/reme-plugin/plugins/reme-expert/commands/reme-distill.md new file mode 100644 index 00000000..95c6cc1b --- /dev/null +++ b/reme-plugin/plugins/reme-expert/commands/reme-distill.md @@ -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. diff --git a/reme-plugin/plugins/reme-expert/commands/reme-recall.md b/reme-plugin/plugins/reme-expert/commands/reme-recall.md new file mode 100644 index 00000000..752f07d4 --- /dev/null +++ b/reme-plugin/plugins/reme-expert/commands/reme-recall.md @@ -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: +--- + +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: []`. + +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. diff --git a/reme-plugin/plugins/reme-expert/hooks/active_events_check.py b/reme-plugin/plugins/reme-expert/hooks/active_events_check.py new file mode 100755 index 00000000..80c79319 --- /dev/null +++ b/reme-plugin/plugins/reme-expert/hooks/active_events_check.py @@ -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", + ) diff --git a/reme-plugin/plugins/reme-expert/hooks/hooks.json b/reme-plugin/plugins/reme-expert/hooks/hooks.json new file mode 100644 index 00000000..e328fef5 --- /dev/null +++ b/reme-plugin/plugins/reme-expert/hooks/hooks.json @@ -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 / create,flip distilled events 的 status,返回 audit 摘要。" + } + ] + } + ], + "Stop": [ + { + "hooks": [ + { + "type": "command", + "command": "${VAULT_PYTHON:-python} ${CLAUDE_PLUGIN_ROOT}/hooks/active_events_check.py", + "timeout": 5 + } + ] + } + ] + } +} diff --git a/reme-plugin/plugins/reme-expert/protocol.md b/reme-plugin/plugins/reme-expert/protocol.md new file mode 100644 index 00000000..f3df6ce7 --- /dev/null +++ b/reme-plugin/plugins/reme-expert/protocol.md @@ -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 + `/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: ` 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. diff --git a/reme-plugin/plugins/reme-expert/skills/reme-expert/SKILL.md b/reme-plugin/plugins/reme-expert/skills/reme-expert/SKILL.md new file mode 100644 index 00000000..e88005a4 --- /dev/null +++ b/reme-plugin/plugins/reme-expert/skills/reme-expert/SKILL.md @@ -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 `. +**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: + description: # 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: + - filename: tool-output.txt + content: + +# Later in the SAME thread (or PreCompact firing) +sync + name: + content: | + ## follow-up + - second-pass facts + materials: + - filename: tool-output.txt # collision → auto-suffixed to tool-output-2.txt + content: +``` + +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//.md + events/2026-05-09//.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 ` | +| **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). diff --git a/reme-plugin/plugins/reme-service/.claude-plugin/plugin.json b/reme-plugin/plugins/reme-service/.claude-plugin/plugin.json new file mode 100644 index 00000000..bbaa0d40 --- /dev/null +++ b/reme-plugin/plugins/reme-service/.claude-plugin/plugin.json @@ -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"] +} diff --git a/reme-plugin/plugins/reme-service/.mcp.json b/reme-plugin/plugins/reme-service/.mcp.json new file mode 100644 index 00000000..fcbcc940 --- /dev/null +++ b/reme-plugin/plugins/reme-service/.mcp.json @@ -0,0 +1,10 @@ +{ + "reme": { + "command": "python", + "args": ["-m", "reme2.mcp.server", "config=reme2/config/service.yaml"], + "env": { + "PYTHONUNBUFFERED": "1", + "PYTHONPATH": "${CLAUDE_PLUGIN_ROOT}/../../.." + } + } +} diff --git a/reme-plugin/plugins/reme-service/README.md b/reme-plugin/plugins/reme-service/README.md new file mode 100644 index 00000000..0ff67462 --- /dev/null +++ b/reme-plugin/plugins/reme-service/README.md @@ -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 + +``` +对话过程 → 模型按需调 retrieve(intent-driven) +任务执行中 → remember(mode=log) 持续 upsert;materials 装原始素材(确定性、零 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. diff --git a/reme-plugin/plugins/reme-service/hooks/active_events_check.py b/reme-plugin/plugins/reme-service/hooks/active_events_check.py new file mode 100755 index 00000000..d626645e --- /dev/null +++ b/reme-plugin/plugins/reme-service/hooks/active_events_check.py @@ -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", + ) diff --git a/reme-plugin/plugins/reme-service/hooks/hooks.json b/reme-plugin/plugins/reme-service/hooks/hooks.json new file mode 100644 index 00000000..35392f6c --- /dev/null +++ b/reme-plugin/plugins/reme-service/hooks/hooks.json @@ -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 / create,flip distilled events 的 status。在 `content` 里写一两行 session 摘要 + 提示,在 `related_paths` 里列本次 sync 过的 event folder 索引路径。\n\nDistill 是 cold-path,handoff 一次就够了,不要每轮调。" + } + ] + } + ], + "Stop": [ + { + "hooks": [ + { + "type": "command", + "command": "${VAULT_PYTHON:-python} ${CLAUDE_PLUGIN_ROOT}/hooks/active_events_check.py", + "timeout": 5 + } + ] + } + ] + } +} diff --git a/reme-plugin/plugins/reme-service/protocol.md b/reme-plugin/plugins/reme-service/protocol.md new file mode 100644 index 00000000..f3df6ce7 --- /dev/null +++ b/reme-plugin/plugins/reme-service/protocol.md @@ -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 + `/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: ` 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. diff --git a/reme-plugin/plugins/reme-service/skills/reme-service/SKILL.md b/reme-plugin/plugins/reme-service/skills/reme-service/SKILL.md new file mode 100644 index 00000000..b9a514e1 --- /dev/null +++ b/reme-plugin/plugins/reme-service/skills/reme-service/SKILL.md @@ -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: + description: # 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: + - filename: tool-output.txt + content: + +# Later in the SAME thread (or PreCompact firing) +remember + mode: log + name: + content: | + ## follow-up + - second-pass facts + materials: + - filename: tool-output.txt # collision → auto-suffixed to tool-output-2.txt + content: +``` + +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: + - + - +``` + +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.