Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devin/wiki.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
},
{
"title": "Prompt Reference",
"purpose": "The 3 MCP prompts, organized in group modules under mcp-core/prompts/ (mirroring the tools/ pattern) with prompt-definitions.ts as the orchestrator — each queries the search index, link graph, and memory layer at invocation time, assembling live vault data with guided instructions. vault-orientation (zero-arg: vault stats with total notes/untagged/broken links, folder note counts, property adoption rates with low-adoption flags, orphan detection via findOrphans, tag cloud, recent notes, memory outline, and contextual tool suggestions based on findings); memory-review (structural overview from listMemoryFiles showing scope callouts, section entry counts, and file sizes before the raw content; reads memory as an append-only evolution; guided reflection adds scope-fit against declared scopes and coverage analysis for gaps); daily-review (outgoing links via getOutgoingLinks with broken-link flags, backlinks via getBacklinks, date-specific activity via modifiedOnDate instead of global recentNotes so past-date reviews show that date's work; guides reconciliation, link following, and pattern recognition). Prompts degrade gracefully — an empty memory layer or missing daily note returns a valid message with getting-started guidance, never a hard failure. Autocomplete is supported on the memory-review file argument.",
"purpose": "The 3 MCP prompts, organized in group modules under mcp-core/prompts/ (mirroring the tools/ pattern) with prompt-definitions.ts as the orchestrator — each queries the search index, link graph, and memory layer at invocation time, assembling live vault data with guided instructions. vault-orientation (zero-arg: vault stats with total notes/untagged/broken links, folder note counts, property adoption rates with low-adoption flags, orphan detection via findOrphans, tag cloud, recent notes, memory outline, and contextual tool suggestions based on findings); memory-review (structural overview from listMemoryFiles showing scope callouts, section entry counts, and file sizes before the raw content; reads memory as an append-only evolution (files marked entry-policy: living are current-state snapshots whose expired entries may be pruned); guided reflection adds scope-fit against declared scopes and coverage analysis for gaps); daily-review (outgoing links via getOutgoingLinks with broken-link flags, backlinks via getBacklinks, date-specific activity via modifiedOnDate instead of global recentNotes so past-date reviews show that date's work; guides reconciliation, link following, and pattern recognition). Prompts degrade gracefully — an empty memory layer or missing daily note returns a valid message with getting-started guidance, never a hard failure. Autocomplete is supported on the memory-review file argument.",
"parent": "MCP Interface"
},
{
Expand Down
6 changes: 5 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,11 @@ from tools:
returns a valid fallback message; a prompt must not hard-fail the client.
- **`memory-review` is append-only by design** — it reads the memory layer
as a dated **evolution** (never "newest supersedes older"), proposes only
append updates, and never prunes "stale" entries.
append updates, and never prunes "stale" entries. The one exception: a
memory file whose frontmatter declares `entry-policy: living` is a
current-state snapshot, and the review may propose pruning its expired
entries (the policy is surfaced by `vault_list_memory_files`; absent
means append-only).

### MCP naming conventions

Expand Down
2 changes: 1 addition & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ Alongside tools, the server registers MCP **prompts** (`prompts/list` / `prompts
| `memory-review` | `file?`, `max_chars?` | Structural overview (scope callouts from `listMemoryFiles`, section entry counts) + dated content as a timeline. Guided reflection: evolution narrative, scope-fit against declared scopes, backfill gaps, coverage analysis. Append-only by design. |
| `daily-review` | `date?`, `max_chars?` | Reconciles a day — daily note content + outgoing links (via `getOutgoingLinks`, with broken-link flags) + backlinks (via `getBacklinks`) + date-specific activity (via `modifiedOnDate`) + vault-wide task status (due/overdue, scheduled, daily-note-scoped via `listTasks`). Surfaces what happened, what's open, and what needs follow-up. |

Each handler degrades to a valid message rather than throwing, so a prompt never hard-fails the client. `memory-review` is deliberately append-only: it reads dated entries as a timeline (each entry true when written), never as "newest supersedes older," and never prunes "stale" entries — matching the memory layer's design. `daily-review` uses `modifiedOnDate` instead of `recentNotes`, so past-date reviews show activity from _that_ date — not today's globally recent notes.
Each handler degrades to a valid message rather than throwing, so a prompt never hard-fails the client. `memory-review` is deliberately append-only: it reads dated entries as a timeline (each entry true when written), never as "newest supersedes older," and never prunes "stale" entries — matching the memory layer's default. The exception is a memory file whose frontmatter declares `entry-policy: living` (a current-state snapshot, e.g. the Routines template): `vault_list_memory_files` surfaces the policy as `entry_policy`, and the review may propose pruning expired entries there. `daily-review` uses `modifiedOnDate` instead of `recentNotes`, so past-date reviews show activity from _that_ date — not today's globally recent notes.

## Infrastructure

Expand Down
8 changes: 6 additions & 2 deletions deploy/local/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,12 @@ File Explorer at `\\wsl$\Ubuntu\home\you\vaults\MyVault`.

On first startup, if your vault doesn't already have a memory folder (default:
`About Me/`), the server creates one with template files (Me.md, Opinions.md,
Principles.md). Agents can also create new memory files and sections on the fly
via `vault_update_memory` — no manual setup needed.
Principles.md, Routines.md, Agents.md). Agents can also create new memory files
and sections on the fly via `vault_update_memory` — no manual setup needed.
Memory files are append-only by default; a file can declare
`entry-policy: living` in frontmatter for current-state content whose expired
entries get pruned (the Routines template ships this way) — see
[templates/memory](../../templates/memory/README.md) for the full convention.

## Configuration

Expand Down
8 changes: 6 additions & 2 deletions deploy/remote/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,12 @@ docker compose down -v

On first startup, if your vault doesn't already have a memory folder (default:
`About Me/`), the server creates one with template files (Me.md, Opinions.md,
Principles.md). Agents can also create new memory files and sections on the fly
via `vault_update_memory` — no manual setup needed.
Principles.md, Routines.md, Agents.md). Agents can also create new memory files
and sections on the fly via `vault_update_memory` — no manual setup needed.
Memory files are append-only by default; a file can declare
`entry-policy: living` in frontmatter for current-state content whose expired
entries get pruned (the Routines template ships this way) — see
[templates/memory](../../templates/memory/README.md) for the full convention.

## Configuration

Expand Down
25 changes: 23 additions & 2 deletions src/vault-mcp/mcp-core/__tests__/memory-review-prompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ describe("memory-review full prompt output", () => {
"",
"1 memory file in About Me/:",
"",
"- **Mem** (98 bytes)",
"- **Mem** (98 bytes, append-only)",
" - Notes (newest first) (1 entries)",
"",
"## Current memory",
Expand All @@ -372,9 +372,30 @@ describe("memory-review full prompt output", () => {
"3. **Backfill gaps.** Point out durable facts that are implied but not yet captured, and propose them as dated append entries (bullet + target file + section).",
"4. **Corrections (rare, separate).** Only a fact that is mis-recorded or now genuinely incorrect — not one that simply changed over time — warrants a fix. Prefer an appended dated correction that preserves the old entry (history matters); reserve vault_delete_memory for genuinely wrong facts.",
"5. **Coverage analysis.** What areas of the user's life, work, or preferences are NOT yet represented? Use the file scopes and section names above to identify gaps worth filling.",
"6. **Expired current-state entries (living files only).** A file marked `living` in the Structure section is a current-state snapshot, not a history ledger — flag entries whose date or commitment has passed and propose pruning them (vault_delete_memory), with the outcome appended to a history section when worth keeping. Never propose this for append-only files.",
"",
"Propose every change as an explicit vault_update_memory call (newest-first; the server stamps the date) and **confirm with me before writing anything**. Never delete an entry just for being old.",
"Propose updates as explicit vault_update_memory calls and deletions as explicit vault_delete_memory calls; for living-file pruning, append any worthwhile outcome to the appropriate history section first. The server stamps update dates. **Confirm with me before writing or deleting anything**. Never delete an entry just for being old from an append-only file.",
].join("\n"),
)
})

it("labels a living file's entry policy in the structural overview", async () => {
const vault = await mkdtemp(join(tmpdir(), "prompt-living-"))
onTestFinished(async () => {
await rm(vault, { recursive: true, force: true })
})
await mkdir(join(vault, "About Me"), { recursive: true })
await writeFile(
join(vault, "About Me", "Routines.md"),
"---\ntitle: Routines\ntype: profile\nentry-policy: living\n---\n\n# Routines\n\n## Upcoming (newest first)\n- **2026-07-11**: a plan\n",
"utf8",
)
const calls = registerWithSearch(vault, {} as SearchIndex)
const handler = findCall(calls, PROMPT_NAMES.MEMORY_REVIEW)[2]

const text = textOf(await handler({ file: "Routines" }, fakeExtra))
// The policy must reach the overview line — this fails if the frontmatter
// property stops flowing through listMemoryFiles into the prompt.
expect(text).toContain("bytes, living)")
})
})
15 changes: 14 additions & 1 deletion src/vault-mcp/mcp-core/__tests__/tool-definitions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,20 @@ describe("registerTools", () => {
// Assert the full contract fragment — a bare "idempotent" check would
// also pass on a reworded "not idempotent" description.
expect(config.description).toContain(
"idempotent — an exact duplicate (same date + text in the same section) is a no-op",
"Idempotent — an exact duplicate (same date + text in the same section) is a no-op",
)
})

it("memory tool descriptions document the entry-policy contract", () => {
// Append-only is the default; the living opt-in must be discoverable from
// the tools that write, delete, and list memory — not only from templates.
const [, updateConfig] = requireCall(TOOL_NAMES.VAULT_UPDATE_MEMORY)
expect(updateConfig.description).toContain("entry-policy: living")
const [, deleteConfig] = requireCall(TOOL_NAMES.VAULT_DELETE_MEMORY)
expect(deleteConfig.description).toContain("entry-policy: living")
const [, listConfig] = requireCall(TOOL_NAMES.VAULT_LIST_MEMORY_FILES)
expect(listConfig.description).toContain(
Comment thread
aliasunder marked this conversation as resolved.
'entry_policy is "append-only" (the default',
)
})

Expand Down
12 changes: 8 additions & 4 deletions src/vault-mcp/mcp-core/prompts/memory-review-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
* The memory layer is append-with-dates, read as an EVOLUTION — never a
* "newest supersedes older" record. This prompt narrates the trajectory and
* proposes append-only changes; it deliberately does not hunt for "stale"
* entries to prune or frame evolving beliefs as contradictions to reconcile. */
* entries to prune or frame evolving beliefs as contradictions to reconcile.
* The one exception is a file whose frontmatter declares `entry-policy:
* living` (a current-state snapshot, e.g. a Routines file): there, expired
* entries are maintenance debt, and the review may propose pruning them. */

import { completable } from "@modelcontextprotocol/sdk/server/completable.js"
import { z } from "zod"
Expand All @@ -26,7 +29,7 @@ export { PROMPT_NAMES as MEMORY_REVIEW_PROMPT_NAMES }

/** Formats a single memory file outline as a bullet with scope and section details. */
const formatFileOutline = (outline: MemoryFileOutline): string => {
const titleLine = `- **${outline.file}** (${outline.bytes} bytes)`
const titleLine = `- **${outline.file}** (${outline.bytes} bytes, ${outline.entry_policy})`

const scopeLines = (outline.leading_callout?.body ?? "")
.split("\n")
Expand Down Expand Up @@ -70,7 +73,7 @@ export const registerMemoryReviewPrompt = ({
PROMPT_NAMES.MEMORY_REVIEW,
{
title: "Reflect on memory (read as an evolution)",
description: `Reflect on the ${config.memoryDir}/ memory layer — review its structure and scopes, read dated entries as a timeline, surface scope-fit issues and coverage gaps, and propose append-only updates. Never prunes entries for being old.`,
description: `Reflect on the ${config.memoryDir}/ memory layer — review its structure and scopes, read dated entries as a timeline, surface scope-fit issues and coverage gaps, and propose append-only updates. Never prunes entries for being old, except expired entries in files marked entry-policy: living.`,
argsSchema: {
file: completable(
z
Expand Down Expand Up @@ -200,8 +203,9 @@ export const registerMemoryReviewPrompt = ({
"3. **Backfill gaps.** Point out durable facts that are implied but not yet captured, and propose them as dated append entries (bullet + target file + section).",
`4. **Corrections (rare, separate).** Only a fact that is mis-recorded or now genuinely incorrect — not one that simply changed over time — warrants a fix. Prefer an appended dated correction that preserves the old entry (history matters); reserve vault_delete_memory for genuinely wrong facts.`,
"5. **Coverage analysis.** What areas of the user's life, work, or preferences are NOT yet represented? Use the file scopes and section names above to identify gaps worth filling.",
"6. **Expired current-state entries (living files only).** A file marked `living` in the Structure section is a current-state snapshot, not a history ledger — flag entries whose date or commitment has passed and propose pruning them (vault_delete_memory), with the outcome appended to a history section when worth keeping. Never propose this for append-only files.",
"",
"Propose every change as an explicit vault_update_memory call (newest-first; the server stamps the date) and **confirm with me before writing anything**. Never delete an entry just for being old.",
"Propose updates as explicit vault_update_memory calls and deletions as explicit vault_delete_memory calls; for living-file pruning, append any worthwhile outcome to the appropriate history section first. The server stamps update dates. **Confirm with me before writing or deleting anything**. Never delete an entry just for being old from an append-only file.",
].join("\n")
reqLogger.info("prompt_result", {
outcome: "ok",
Expand Down
Loading
Loading