Skip to content
Open
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 CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Thank you for considering contributing to Mnemon!
- Bug fixes with a reproducing test or E2E scenario
- Performance improvements with benchmark evidence
- Documentation improvements (typos, clarity, missing examples)
- New integrations for LLM CLIs beyond Claude Code and OpenClaw
- New integrations for agent runtimes and MCP clients

For significant features or architectural changes, please **open an issue first** to discuss the approach before writing code.

Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,20 @@ DETERMINISTIC_PKGS := \
. \
./cmd \
./cmd/agency \
./cmd/mcp \
./cmd/memory \
./internal/agency \
./internal/agency/client \
./internal/agency/attach \
./internal/agency/authority \
./internal/agency/artifact \
./internal/memory/embed \
./internal/mcp \
./internal/memory/graph \
./internal/memory/importdraft \
./internal/memory/model \
./internal/memory/search \
./internal/memory/service \
./internal/memory/setup \
./internal/memory/setup/assets \
./internal/memory/store \
Expand Down
45 changes: 37 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ LLM agents forget everything between sessions. Context compaction drops critical

Mnemon gives your agent persistent, cross-session memory — a four-graph knowledge store with intent-aware recall, importance decay, and automatic deduplication. The `mnemon` memory path remains one local binary with zero API keys and one setup command.

Mnemon ships one executable with two separate surfaces. Memory stays at the
`mnemon` root; [Agency Preview](docs/AGENCY.md) lives at `mnemon agency ...` and adds
durable, project-local responsibility and effect admission to an existing Pi
agent. Agency does not replace Memory or the Agent Runtime.
Mnemon ships one executable with three entry points. Memory stays at the
`mnemon` root; `mnemon mcp serve` exposes the same Memory engine to MCP clients;
and [Agency Preview](docs/AGENCY.md) lives at `mnemon agency ...`, adding durable,
project-local responsibility and effect admission to an existing Pi agent.
Neither MCP nor Agency replaces Memory or the Agent Runtime.

> **Claude Max / Pro subscriber?** Mnemon works entirely through your existing subscription — no separate API key required. Your LLM subscription *is* the intelligence layer. Two commands and you're done.

Expand All @@ -37,6 +38,9 @@ Most memory tools embed their own LLM inside the pipeline. Mnemon takes a differ
| **MCP Server** | Tool provider via MCP protocol | claude-mem |
| **LLM-Supervised** | External supervisor of a standalone binary | **Mnemon** |

Mnemon's built-in MCP server is a transport adapter for the same deterministic,
LLM-supervised engine; it does not embed another LLM or require another API key.

Mnemon also addresses a gap in the protocol stack. MCP standardizes how LLMs discover and invoke tools. ODBC/JDBC standardizes how applications access databases. But how LLMs interact with databases using memory semantics — this layer has no protocol. Mnemon's three primitives — `remember`, `link`, `recall` — form an intent-native protocol: command names map to the LLM's cognitive vocabulary (`remember` not INSERT, `recall` not SELECT), and output is structured JSON with signal transparency rather than raw database rows.

<p align="center">
Expand Down Expand Up @@ -71,7 +75,7 @@ brew install --cask mnemon-dev/tap/mnemon
go install github.com/mnemon-dev/mnemon@latest
```

Windows supports the core Memory commands. Agency remains unavailable on
Windows supports the core Memory commands and MCP server. Agency remains unavailable on
Windows until its local authority boundary has native Windows security.

**From source** (macOS / Linux):
Expand All @@ -85,6 +89,7 @@ make install

```bash
mnemon --version
mnemon mcp --help
mnemon agency --version
```

Expand All @@ -100,6 +105,29 @@ enables Memory, while the command above enables Agency. See the
[Agency guide](docs/AGENCY.md) for its operating model, Preview compatibility
boundary, and optional peers.

### MCP clients

Any stdio MCP client can use Mnemon's six Memory tools through the same binary.
For clients using the conventional `mcpServers` configuration shape, add:

```json
{
"mcpServers": {
"mnemon": {
"command": "mnemon",
"args": ["mcp", "serve"]
}
}
}
```

To select a store, place root flags before the namespace, for example
`["--store", "work", "mcp", "serve"]`, or set `MNEMON_STORE`. The server
offers `recall`, `search`, `remember`, `related`, `link`, and `status`. Read
results are capped at 600 Unicode characters per memory by default; clients can
request `full: true` when complete content is needed. See the
[MCP reference](docs/USAGE.md#mcp-server) for all limits and options.

### [Claude Code](https://github.com/anthropics/claude-code)

```bash
Expand Down Expand Up @@ -344,7 +372,8 @@ memory is useful.

- **Zero user-side operation** — install once; supported runtimes can use hooks, minimal runtimes can use persistent rules
- **LLM-supervised** — the host LLM decides what to remember, update, and forget; no embedded LLM, no API keys
- **Multi-framework support** — Claude Code, Codex, Cursor, ZCode, TRAE/TRAE Work, Qoder/QoderWork, CodeBuddy, WorkBuddy, Kimi Code, OpenCode, and Hermes Agent (hooks/plugins), OpenClaw (plugins), Pi (extensions), MiniMax Code and Nanobot (skills), DeepSeek Harness (via the dsh-mnemon plugin), and more
- **Built-in MCP server** — one stdio command exposes six bounded, schema-described Memory tools to MCP clients
- **Multi-framework support** — Claude Code, Codex, Cursor, ZCode, TRAE/TRAE Work, Qoder/QoderWork, CodeBuddy, WorkBuddy, Kimi Code, OpenCode, and Hermes Agent (hooks/plugins), OpenClaw (plugins), Pi (extensions), MiniMax Code and Nanobot (skills), DeepSeek Harness (via the dsh-mnemon plugin), any stdio MCP client, and more
- **Runtime-native integration** — runtime-specific `SKILL.md`, shared `guide.md`, and supported hooks or extensions
- **Four-graph architecture** — temporal, entity, causal, and semantic edges, not just vector similarity
- **Intent-native protocol** — three primitives (`remember`, `link`, `recall`) map to the LLM's cognitive vocabulary, not database syntax; structured JSON output with signal transparency
Expand Down Expand Up @@ -490,7 +519,7 @@ mnemon setup --eject # remove all integrations
make help # show all targets
```

**Dependencies**: Go 1.24+, `modernc.org/sqlite`, `spf13/cobra`, `google/uuid`
**Dependencies**: Go 1.24+, `modernc.org/sqlite`, `spf13/cobra`, `google/uuid`, `modelcontextprotocol/go-sdk`

See [Development and Deployment](docs/DEPLOYMENT.md) for Docker, Compose, Ollama embedding, and release setup.

Expand All @@ -499,7 +528,7 @@ See [Development and Deployment](docs/DEPLOYMENT.md) for Docker, Compose, Ollama
- [Agency Preview](docs/AGENCY.md) — maturity boundary, Pi setup, operating model, completion semantics, and optional peers
- [Go Engineering Standard](docs/development/go-engineering-standard.md) — maintainability, concurrency, persistence, testing, and review thresholds
- [Design & Architecture](docs/DESIGN.md) — current engine architecture, algorithms, integration design
- [Memory Usage & Reference](docs/USAGE.md) — root Memory commands, import, receipts, and embedding support
- [Memory & MCP Usage Reference](docs/USAGE.md) — root Memory commands, built-in MCP server, import, receipts, and embedding support
- [Memory Import Guide](docs/IMPORT.md) — schema and LLM prompt for importing historical chats
- [Architecture Diagrams](docs/diagrams/) — system architecture, pipelines, lifecycle management

Expand Down
37 changes: 37 additions & 0 deletions cmd/mcp/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Package mcp composes Mnemon's Model Context Protocol command namespace.
package mcp

import (
"io"

mcpserver "github.com/mnemon-dev/mnemon/internal/mcp"
memoryservice "github.com/mnemon-dev/mnemon/internal/memory/service"
"github.com/spf13/cobra"
)

// ConfigProvider resolves the parsed product-level Memory flags at execution
// time.
type ConfigProvider func(warnings io.Writer) memoryservice.Config

// New returns the `mnemon mcp` command namespace.
func New(version string, provideConfig ConfigProvider) *cobra.Command {
root := &cobra.Command{
Use: "mcp",
Short: "Expose Mnemon memory through the Model Context Protocol",
}
serve := &cobra.Command{
Use: "serve",
Short: "Serve MCP over standard input and output",
Args: cobra.NoArgs,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, _ []string) error {
config := provideConfig(cmd.ErrOrStderr())
config.AuditContent = false
memory := memoryservice.New(config)
server := mcpserver.New(version, memory)
return server.Serve(cmd.Context(), cmd.InOrStdin(), cmd.OutOrStdout())
},
}
root.AddCommand(serve)
return root
}
24 changes: 24 additions & 0 deletions cmd/mcp/command_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package mcp

import (
"io"
"testing"

memoryservice "github.com/mnemon-dev/mnemon/internal/memory/service"
)

func TestNewBuildsServeNamespace(t *testing.T) {
command := New("test-version", func(io.Writer) memoryservice.Config {
return memoryservice.Config{DataDir: t.TempDir()}
})
if command.Use != "mcp" {
t.Fatalf("use = %q, want mcp", command.Use)
}
serve, _, err := command.Find([]string{"serve"})
if err != nil || serve == command || serve.Use != "serve" {
t.Fatalf("serve command is not registered: %v", err)
}
if !serve.SilenceUsage {
t.Fatal("serve command must suppress CLI usage on protocol failures")
}
}
78 changes: 7 additions & 71 deletions cmd/memory/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import (
"encoding/json"
"fmt"
"os"
"time"

"github.com/mnemon-dev/mnemon/internal/memory/model"
memoryservice "github.com/mnemon-dev/mnemon/internal/memory/service"
"github.com/spf13/cobra"
)

Expand All @@ -22,85 +21,22 @@ var linkCmd = &cobra.Command{
Long: "Create or update a typed edge between two insights. Used by Claude to create semantic edges after evaluating candidates.",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
sourceID := args[0]
targetID := args[1]

// Validate edge type
edgeType := model.EdgeType(linkType)
if !model.ValidEdgeTypes[edgeType] {
return fmt.Errorf("invalid edge type %q; valid: temporal, semantic, causal, entity", linkType)
}

// Validate weight
if linkWeight < 0.0 || linkWeight > 1.0 {
return fmt.Errorf("weight must be between 0.0 and 1.0, got %.2f", linkWeight)
}

db, err := openWritableDB("link")
if err != nil {
return fmt.Errorf("open database: %w", err)
}
defer db.Close()

// Validate both insights exist
src, err := db.GetInsightByID(sourceID)
if err != nil || src == nil {
return fmt.Errorf("source insight %s not found", sourceID)
}
tgt, err := db.GetInsightByID(targetID)
if err != nil || tgt == nil {
return fmt.Errorf("target insight %s not found", targetID)
}

// Parse optional metadata
metadata := map[string]string{"created_by": "claude"}
metadata := map[string]string{}
if linkMeta != "" {
if err := json.Unmarshal([]byte(linkMeta), &metadata); err != nil {
return fmt.Errorf("invalid metadata JSON: %w", err)
}
metadata["created_by"] = "claude"
}

now := time.Now().UTC()

// Create bidirectional edges (INSERT OR REPLACE)
err = db.InsertEdge(&model.Edge{
SourceID: sourceID,
TargetID: targetID,
EdgeType: edgeType,
Weight: linkWeight,
Metadata: metadata,
CreatedAt: now,
})
if err != nil {
return fmt.Errorf("create edge %s→%s: %w", sourceID, targetID, err)
}

err = db.InsertEdge(&model.Edge{
SourceID: targetID,
TargetID: sourceID,
EdgeType: edgeType,
Weight: linkWeight,
Metadata: metadata,
CreatedAt: now,
result, err := newRuntimeService(os.Stderr).Link(cmd.Context(), memoryservice.LinkRequest{
SourceID: args[0], TargetID: args[1], EdgeType: linkType,
Weight: linkWeight, Metadata: metadata, CreatedBy: "claude",
})
if err != nil {
return fmt.Errorf("create edge %s→%s: %w", targetID, sourceID, err)
}

db.LogOp("link", sourceID, fmt.Sprintf("%s→%s type=%s weight=%.2f", truncID(sourceID), truncID(targetID), linkType, linkWeight))

output := map[string]interface{}{
"status": "linked",
"source_id": sourceID,
"target_id": targetID,
"edge_type": linkType,
"weight": linkWeight,
"metadata": metadata,
return err
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(output)
return enc.Encode(result)
},
}

Expand Down
67 changes: 10 additions & 57 deletions cmd/memory/recall.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@ import (
"os"
"strings"

"github.com/mnemon-dev/mnemon/internal/memory/embed"
"github.com/mnemon-dev/mnemon/internal/memory/graph"
"github.com/mnemon-dev/mnemon/internal/memory/search"
"github.com/mnemon-dev/mnemon/internal/memory/store"
memoryservice "github.com/mnemon-dev/mnemon/internal/memory/service"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -115,31 +113,18 @@ var recallCmd = &cobra.Command{
return fmt.Errorf("--brief and --verbose cannot be used together")
}

db, err := openDB()
if err != nil {
return fmt.Errorf("open database: %w", err)
}
defer db.Close()

enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
response, err := newRuntimeService(os.Stderr).Recall(cmd.Context(), memoryservice.RecallRequest{
Query: keyword, Category: recCategory, Source: recSource,
Limit: recLimit, Basic: recBasic, Intent: recIntent,
})
if err != nil {
return err
}

if recBasic {
// Legacy SQL LIKE recall.
results, err := db.QueryInsights(store.QueryFilter{
Keyword: keyword,
Category: recCategory,
Source: recSource,
Limit: recLimit,
})
if err != nil {
return fmt.Errorf("query insights: %w", err)
}

for _, r := range results {
_ = db.IncrementAccessCount(r.ID)
}
db.LogOp("recall:basic", "", fmt.Sprintf("q=%s hits=%d", keyword, len(results)))
results := response.BasicResults
if recBrief {
brief := make([]briefResult, 0, len(results))
for _, result := range results {
Expand All @@ -154,39 +139,7 @@ var recallCmd = &cobra.Command{
return enc.Encode(results)
}

// Default: intent-aware graph-enhanced recall
var intentOverride *search.Intent
if recIntent != "" {
parsed, err := search.IntentFromString(recIntent)
if err != nil {
return err
}
intentOverride = &parsed
}

// Try to get query embedding for hybrid search
var queryVec []float64
ec := embed.NewClientWithModel(resolveEmbedModel())
if ec.Available() {
queryVec, _ = ec.Embed(keyword)
}

// Extract query entities at cmd layer (avoid graph->search circular dep).
// Load the known-entity set so the indexed extractor's fourth path can
// admit user vocabulary (single-segment CamelCase, lowercase project
// names) that techDictionary does not cover. The lookup is read-only;
// on error we fall through to the default regex+dictionary extractor.
knownEntities, _ := db.LoadKnownEntities()
queryEntities := graph.ExtractEntitiesIndexed(keyword, knownEntities)

resp, err := search.IntentAwareRecall(db, keyword, queryVec, queryEntities, recLimit, intentOverride)
if err != nil {
return fmt.Errorf("recall: %w", err)
}
for _, r := range resp.Results {
_ = db.IncrementAccessCount(r.Insight.ID)
}
db.LogOp("recall", "", fmt.Sprintf("q=%s hits=%d", keyword, len(resp.Results)))
resp := *response.SmartResults

if recVerbose {
return enc.Encode(resp)
Expand Down
Loading
Loading