From bae4f951de342fa39cbcc4be72355f6670840d94 Mon Sep 17 00:00:00 2001 From: rrader2890 Date: Wed, 22 Jul 2026 17:07:23 -0400 Subject: [PATCH 1/4] fix(extraction): keep substantive input as raw observation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit observe() ran only first-person-anchored rules (^i prefer, ^we decided) and silently discarded everything else — third-person statements, emails, most natural prose. A local user feeding raw text saw nothing persist, which defeats the core 'feed it text and it remembers' UX. Add a fallback in extract(): when no structured rule matches but the input is substantive prose (not a question, not code, not an ack), persist it verbatim as a raw 'observation' memory. Structured rules still win when they match; hosted LLM extraction remains the premium that refines these into typed facts. Fixes the smoke test: 1/4 -> 4/4 statements captured. Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/core/src/extraction.rs | 75 +++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/crates/core/src/extraction.rs b/crates/core/src/extraction.rs index 2055e30..1bce779 100644 --- a/crates/core/src/extraction.rs +++ b/crates/core/src/extraction.rs @@ -73,9 +73,52 @@ pub fn extract(text: &str, ctx: &ObserveContext) -> Vec { out.push(m); } } + + // Fallback: nothing matched a structured rule, but the input is + // substantive prose. Keep it verbatim as a raw `observation` rather than + // silently dropping it — an agent memory that discards what the user tells + // it is worse than one that over-captures. The structured rules are + // first-person-anchored ("I prefer…", "we decided…"); most real input + // (third-person, questions aside, arbitrary facts) matches nothing, so + // without this the store stays empty. Hosted LLM extraction refines these + // into typed facts; locally we at least never lose them. + if out.is_empty() { + if let Some(m) = fallback_observation(text) { + out.push(m); + } + } + out } +/// Keep substantive input that matched no structured rule as a raw +/// `observation`. Filters out questions, code/commands, and short fragments so +/// the store fills with statements rather than chatter or one-word acks. +fn fallback_observation(text: &str) -> Option { + let trimmed = text.trim(); + if is_code_or_command(trimmed) { + return None; + } + // Questions ask, they don't assert — skip them. + if trimmed.ends_with('?') { + return None; + } + // Require some substance so acks that slipped past the filler filter + // ("sounds good to me") don't become memories. + let word_count = trimmed.split_whitespace().count(); + if trimmed.chars().count() < 24 || word_count < 4 { + return None; + } + Some(ExtractedMemory { + content: normalize_subject(trimmed), + kind: "observation", + scope: MemoryScope::Project, + importance: 3.0, + impact: MemoryImpact::Low, + reason: "raw-observation", + }) +} + /// Split a message into sentence-ish lines for per-clause extraction. Real /// NLP-grade sentence splitting is overkill; we just want to keep distinct /// statements distinct. @@ -546,4 +589,36 @@ mod tests { let out = extract("What do you think we should do here?", &ctx()); assert!(out.is_empty()); } + + #[test] + fn third_person_statement_kept_as_raw_observation() { + // Matches no first-person rule, but must not be dropped. + let out = extract("Ryan prefers pnpm over npm for all projects.", &ctx()); + assert_eq!(out.len(), 1); + assert_eq!(out[0].kind, "observation"); + assert_eq!(out[0].reason, "raw-observation"); + assert!(out[0].content.to_lowercase().contains("pnpm")); + } + + #[test] + fn arbitrary_fact_captured_via_fallback() { + let out = extract("Ryan's email is ryan@thinkfleet.ai for work.", &ctx()); + assert_eq!(out.len(), 1); + assert_eq!(out[0].kind, "observation"); + } + + #[test] + fn structured_rule_still_wins_over_fallback() { + // A first-person preference should classify as `preference`, not the + // generic `observation` fallback. + let out = extract("I prefer Vitest over Jest for testing.", &ctx()); + assert_eq!(out.len(), 1); + assert_eq!(out[0].kind, "preference"); + } + + #[test] + fn fallback_skips_questions_and_short_fragments() { + assert!(extract("What should we do about the migration here?", &ctx()).is_empty()); + assert!(extract("Sounds good to me", &ctx()).is_empty()); + } } From e04ed605b48a993d3af8faa2712ba6742ee95ba4 Mon Sep 17 00:00:00 2001 From: rrader2890 Date: Wed, 22 Jul 2026 17:09:28 -0400 Subject: [PATCH 2/4] feat(mcp): expose commodity CRUD tools (delete, supersede, stats) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The OSS MCP server exposed 7 tools; the plugin's forget/dream/stats/health skills call memory_delete / memory_supersede / memory_stats, which only existed on the proprietary superset — so those skills errored on a local install. These three are basic hygiene, not moat: the Storage trait already implements delete/supersede/count_items. Wire them into the MCP surface. OSS MCP surface: 7 -> 10 tools. Prediction / graph-reasoning / build-context remain proprietary-only (the actual moat). Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/mcp/src/lib.rs | 56 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/crates/mcp/src/lib.rs b/crates/mcp/src/lib.rs index 0069a00..ba1535d 100644 --- a/crates/mcp/src/lib.rs +++ b/crates/mcp/src/lib.rs @@ -324,6 +324,38 @@ fn tool_definitions() -> serde_json::Value { } } } + }, + { + "name": "memory_delete", + "description": "Delete a memory by id. Soft-delete by default (marks it deleted, recoverable); pass hard=true to remove the row permanently. Use to correct a mistake or honor a 'forget this' request.", + "inputSchema": { + "type": "object", + "required": ["id"], + "properties": { + "id": { "type": "string", "description": "id of the memory to delete." }, + "hard": { "type": "boolean", "default": false, "description": "true = permanent hard delete; false = recoverable soft delete." } + } + } + }, + { + "name": "memory_supersede", + "description": "Mark one memory as superseded by another — the old memory is kept for audit/history but is no longer the current truth. Use when a fact changes ('actually we moved to Postgres'): save the new memory, then supersede the old one by the new id.", + "inputSchema": { + "type": "object", + "required": ["id", "byId"], + "properties": { + "id": { "type": "string", "description": "id of the memory being superseded (the old / outdated one)." }, + "byId": { "type": "string", "description": "id of the memory that replaces it (the new current one)." } + } + } + }, + { + "name": "memory_stats", + "description": "Return counts about the memory store — currently the total number of memories held. Useful for a health check or a 'how much do you remember' summary.", + "inputSchema": { + "type": "object", + "properties": {} + } } ]) } @@ -421,6 +453,30 @@ async fn handle_tool_call( "memory_commit_extraction" => commit_extraction(storage, args).await, + "memory_delete" => { + let id = arg_str(args, "id")?; + let hard = args.get("hard").and_then(|v| v.as_bool()).unwrap_or(false); + storage.delete(id, hard).await?; + Ok(text_result(&format!( + "{} memory {id}", + if hard { "hard-deleted" } else { "deleted" } + ))) + } + + "memory_supersede" => { + let id = arg_str(args, "id")?; + let by_id = arg_str(args, "byId")?; + storage.supersede(id, by_id).await?; + Ok(text_result(&format!("superseded {id} by {by_id}"))) + } + + "memory_stats" => { + let total = storage.count_items().await?; + Ok(text_result(&serde_json::to_string_pretty( + &serde_json::json!({ "totalMemories": total }), + )?)) + } + other => anyhow::bail!("unknown tool: {other}"), } } From 1df850aac054bf818f63198e2d7eb6e9a790b152 Mon Sep 17 00:00:00 2001 From: rrader2890 Date: Wed, 22 Jul 2026 17:11:25 -0400 Subject: [PATCH 3/4] docs(plugin): hosted-gate moat skills + retune descriptions The plugin shipped predict/behaviors/why/context-loader/graph skills that call MCP tools only the hosted engine registers (memory_predict, build_context, graph_reason, query_graph, prefetch_related). On a local/OSS install they'd error. Add a 'requires hosted mode' banner to each so the agent degrades to search/recall instead of calling a missing tool; graph notes that BUILDING the graph works locally, only reasoning needs hosted. Retune plugin + marketplace descriptions to lead with the local-first memory store (the OSS value) and frame prediction/behavior as the hosted upgrade, rather than advertising moat features a local install can't run. Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude-plugin/marketplace.json | 2 +- integrations/memmesh-plugin/.claude-plugin/plugin.json | 2 +- integrations/memmesh-plugin/skills/behaviors/SKILL.md | 3 +++ integrations/memmesh-plugin/skills/context-loader/SKILL.md | 3 +++ integrations/memmesh-plugin/skills/graph/SKILL.md | 3 +++ integrations/memmesh-plugin/skills/predict/SKILL.md | 3 +++ integrations/memmesh-plugin/skills/why/SKILL.md | 3 +++ 7 files changed, 17 insertions(+), 2 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 679aa87..8ec58ac 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -11,7 +11,7 @@ { "name": "memmesh", "source": "./integrations/memmesh-plugin", - "description": "MemMesh — a memory + calibrated-prediction + behavior-discovery engine for AI apps. Persistent hierarchical memory, a bi-temporal knowledge graph, and forward predictions with provenance and abstention. The engine decides what to save; your agent's own model does any extraction (zero engine-side LLM cost).", + "description": "MemMesh — persistent, local-first memory for AI agents. Bi-temporal knowledge graph, typed/scoped recall, and local↔server sync, over MCP; runs from one binary on SQLite with no mandatory LLM calls (your agent's own model does any extraction). Calibrated prediction & behavior discovery available in hosted mode.", "version": "0.1.0" } ] diff --git a/integrations/memmesh-plugin/.claude-plugin/plugin.json b/integrations/memmesh-plugin/.claude-plugin/plugin.json index 49ee1e6..eb450c1 100644 --- a/integrations/memmesh-plugin/.claude-plugin/plugin.json +++ b/integrations/memmesh-plugin/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "memmesh", "version": "0.1.0", - "description": "Persistent memory + calibrated prediction for AI agents. Remembers decisions, preferences, and patterns across sessions; predicts what a subject will do next — with provenance and honest abstention.", + "description": "Persistent, local-first memory for AI agents. Remembers facts, preferences, and decisions across sessions with a bi-temporal knowledge graph, over MCP — runs fully local on SQLite with no mandatory LLM calls. Calibrated prediction & behavior discovery available in hosted mode.", "author": { "name": "ThinkFleet", "email": "support@memmesh.ai" diff --git a/integrations/memmesh-plugin/skills/behaviors/SKILL.md b/integrations/memmesh-plugin/skills/behaviors/SKILL.md index 1a019ca..8c60a37 100644 --- a/integrations/memmesh-plugin/skills/behaviors/SKILL.md +++ b/integrations/memmesh-plugin/skills/behaviors/SKILL.md @@ -9,6 +9,9 @@ description: > # behaviors +> **⚙️ Requires MemMesh hosted mode.** Calibrated prediction and behavior discovery run on the hosted engine — set your `mm-` API key. On a local / open-source install these tools (`memory_predict`, `memory_build_context`) are not registered; if a call returns "unknown tool", tell the user this is a hosted capability and fall back to `search` / `recall` for what's already known. + + Show the patterns MemMesh discovered on its own. These `behavior_pattern` memories are what `predict` projects forward — inspecting them explains the forecasts. diff --git a/integrations/memmesh-plugin/skills/context-loader/SKILL.md b/integrations/memmesh-plugin/skills/context-loader/SKILL.md index dcf122a..1d28bdf 100644 --- a/integrations/memmesh-plugin/skills/context-loader/SKILL.md +++ b/integrations/memmesh-plugin/skills/context-loader/SKILL.md @@ -10,6 +10,9 @@ description: > # context-loader +> **⚙️ Requires MemMesh hosted mode.** Calibrated prediction and behavior discovery run on the hosted engine — set your `mm-` API key. On a local / open-source install these tools (`memory_predict`, `memory_build_context`) are not registered; if a call returns "unknown tool", tell the user this is a hosted capability and fall back to `search` / `recall` for what's already known. + + Prime the session with the right memory before you act. ## General project/session context diff --git a/integrations/memmesh-plugin/skills/graph/SKILL.md b/integrations/memmesh-plugin/skills/graph/SKILL.md index 809a02f..5c804a8 100644 --- a/integrations/memmesh-plugin/skills/graph/SKILL.md +++ b/integrations/memmesh-plugin/skills/graph/SKILL.md @@ -9,6 +9,9 @@ description: > # graph +> **⚙️ Graph reasoning requires MemMesh hosted mode.** Building the graph works locally: `memory_extract_pending` → `memory_commit_extraction` populate typed entities/edges. Multi-hop **reasoning/traversal** (`memory_graph_reason`, `memory_query_graph`, `memory_prefetch_related`) runs on the hosted engine — set your `mm-` API key. If those return "unknown tool" on a local install, say so and use `search` over the extracted entities instead. + + MemMesh links memories into a knowledge graph whose edges are **bi-temporal** (each has `valid_from` / `valid_to`). That enables answers a flat store can't give. diff --git a/integrations/memmesh-plugin/skills/predict/SKILL.md b/integrations/memmesh-plugin/skills/predict/SKILL.md index f2a59e1..50cc8c3 100644 --- a/integrations/memmesh-plugin/skills/predict/SKILL.md +++ b/integrations/memmesh-plugin/skills/predict/SKILL.md @@ -10,6 +10,9 @@ description: > # predict +> **⚙️ Requires MemMesh hosted mode.** Calibrated prediction and behavior discovery run on the hosted engine — set your `mm-` API key. On a local / open-source install these tools (`memory_predict`, `memory_build_context`) are not registered; if a call returns "unknown tool", tell the user this is a hosted capability and fall back to `search` / `recall` for what's already known. + + Turn accumulated memory into a forward forecast. Unlike `search` ("what do we know"), `predict` answers "what happens next" — and it tells you how confident it honestly is, or abstains. diff --git a/integrations/memmesh-plugin/skills/why/SKILL.md b/integrations/memmesh-plugin/skills/why/SKILL.md index 9e8b4a4..2e0b0d6 100644 --- a/integrations/memmesh-plugin/skills/why/SKILL.md +++ b/integrations/memmesh-plugin/skills/why/SKILL.md @@ -10,6 +10,9 @@ description: > # why +> **⚙️ Requires MemMesh hosted mode.** Calibrated prediction and behavior discovery run on the hosted engine — set your `mm-` API key. On a local / open-source install these tools (`memory_predict`, `memory_build_context`) are not registered; if a call returns "unknown tool", tell the user this is a hosted capability and fall back to `search` / `recall` for what's already known. + + Make MemMesh's outputs auditable. Every prediction and consolidated fact carries provenance and a calibrated confidence — this skill exposes them so a human can check the reasoning. From 8b605c6a5c69955bea25cae0e32f7ef9027bc024 Mon Sep 17 00:00:00 2001 From: rrader2890 Date: Wed, 22 Jul 2026 17:15:05 -0400 Subject: [PATCH 4/4] chore(release): v0.1.1 Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude-plugin/marketplace.json | 2 +- Cargo.lock | 22 +++++++++---------- Cargo.toml | 2 +- .../memmesh-plugin/.claude-plugin/plugin.json | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 8ec58ac..c39dfce 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -12,7 +12,7 @@ "name": "memmesh", "source": "./integrations/memmesh-plugin", "description": "MemMesh — persistent, local-first memory for AI agents. Bi-temporal knowledge graph, typed/scoped recall, and local↔server sync, over MCP; runs from one binary on SQLite with no mandatory LLM calls (your agent's own model does any extraction). Calibrated prediction & behavior discovery available in hosted mode.", - "version": "0.1.0" + "version": "0.1.1" } ] } diff --git a/Cargo.lock b/Cargo.lock index 2366ad5..87bb811 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2065,7 +2065,7 @@ checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "memory-audit" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "async-trait", @@ -2080,7 +2080,7 @@ dependencies = [ [[package]] name = "memory-cli" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "chrono", @@ -2104,7 +2104,7 @@ dependencies = [ [[package]] name = "memory-core" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "chrono", @@ -2121,7 +2121,7 @@ dependencies = [ [[package]] name = "memory-embed" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "async-trait", @@ -2135,7 +2135,7 @@ dependencies = [ [[package]] name = "memory-embed-server" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "axum", @@ -2149,7 +2149,7 @@ dependencies = [ [[package]] name = "memory-eval" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "async-trait", @@ -2166,7 +2166,7 @@ dependencies = [ [[package]] name = "memory-license" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "base64 0.22.1", @@ -2183,7 +2183,7 @@ dependencies = [ [[package]] name = "memory-mcp" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "async-trait", @@ -2201,7 +2201,7 @@ dependencies = [ [[package]] name = "memory-server" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "axum", @@ -2224,7 +2224,7 @@ dependencies = [ [[package]] name = "memory-storage" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "async-trait", @@ -2244,7 +2244,7 @@ dependencies = [ [[package]] name = "memory-sync" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 5c1d260..82c5689 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ members = [ ] [workspace.package] -version = "0.1.0" +version = "0.1.1" edition = "2021" authors = ["MemMesh contributors"] license = "Apache-2.0" diff --git a/integrations/memmesh-plugin/.claude-plugin/plugin.json b/integrations/memmesh-plugin/.claude-plugin/plugin.json index eb450c1..7871fc2 100644 --- a/integrations/memmesh-plugin/.claude-plugin/plugin.json +++ b/integrations/memmesh-plugin/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "memmesh", - "version": "0.1.0", + "version": "0.1.1", "description": "Persistent, local-first memory for AI agents. Remembers facts, preferences, and decisions across sessions with a bi-temporal knowledge graph, over MCP — runs fully local on SQLite with no mandatory LLM calls. Calibrated prediction & behavior discovery available in hosted mode.", "author": { "name": "ThinkFleet",