Skip to content

Commit 80294c1

Browse files
Grivnclaude
andcommitted
Add named memory stores for data isolation
Support multiple isolated memory stores under ~/.mnemon/data/<name>/, selectable via --store flag, MNEMON_STORE env var, or ~/.mnemon/active file. Includes automatic migration of legacy ~/.mnemon/mnemon.db to the new data/default/ layout, and setup now initializes the default store. New commands: mnemon store list|create|set|remove Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8d6fea7 commit 80294c1

16 files changed

Lines changed: 605 additions & 20 deletions

File tree

.claude/skills/mnemon/SKILL.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ mnemon gc --threshold 0.4
2929
mnemon gc --keep <id>
3030
mnemon status
3131
mnemon log
32+
mnemon store list
33+
mnemon store create <name>
34+
mnemon store set <name>
35+
mnemon store remove <name>
3236
```
3337

3438
## Guardrails

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
88
## [Unreleased]
99

1010
### Added
11+
- Named memory stores for data isolation (`mnemon store list|create|set|remove`)
12+
- `MNEMON_STORE` environment variable and `--store` CLI flag for store selection
13+
- Automatic migration of legacy `~/.mnemon/mnemon.db` to `~/.mnemon/data/default/`
14+
- `mnemon setup` now initializes the default store automatically
1115
- Release pipeline: GoReleaser, GitHub Actions, Homebrew tap
1216
- `--version` flag
1317
- CONTRIBUTING.md, CHANGELOG.md

README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,18 @@ The foundation is in place: a single `~/.mnemon` database that any agent can rea
152152
## FAQ
153153

154154
**Do different sessions share memory?**
155-
Yes. All sessions use the same `~/.mnemon` database — a decision remembered in one session is available in every future session.
155+
Yes. By default, all sessions use the same `default` store — a decision remembered in one session is available in every future session.
156+
157+
**Can I isolate memory per project or agent?**
158+
Yes. Use named stores to separate memory:
159+
160+
```bash
161+
mnemon store create work # create a new store
162+
mnemon store set work # set as default
163+
MNEMON_STORE=work mnemon recall "query" # or use env var per-process
164+
```
165+
166+
Different agents/processes can use different stores via the `MNEMON_STORE` environment variable — no global state contention.
156167

157168
**Local or global mode?**
158169
`mnemon setup` defaults to **local** (project-scoped `.claude/`), recommended for most users. **Global** (`mnemon setup --global`, installed to `~/.claude/`) activates mnemon across all projects — convenient if you want other frameworks (e.g., OpenClaw) to share memory by forwarding requests through Claude Code CLI, but may add maintenance overhead.
@@ -167,7 +178,8 @@ Memory writes don't happen in the main conversation. The host LLM (e.g., Opus) d
167178

168179
| Environment Variable | Default | Description |
169180
|---|---|---|
170-
| `MNEMON_DATA_DIR` | `~/.mnemon` | Database directory |
181+
| `MNEMON_DATA_DIR` | `~/.mnemon` | Base data directory |
182+
| `MNEMON_STORE` | *(active file or `default`)* | Named memory store for data isolation |
171183

172184
**Ollama-specific** (only relevant if using embeddings):
173185

cmd/root.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ import (
1111
// version is set at build time via ldflags.
1212
var version = "dev"
1313

14-
var dataDir string
14+
var (
15+
dataDir string
16+
storeName string
17+
)
1518

1619
var rootCmd = &cobra.Command{
1720
Use: "mnemon",
@@ -28,10 +31,28 @@ func Execute() {
2831
}
2932

3033
func init() {
31-
rootCmd.PersistentFlags().StringVar(&dataDir, "data-dir", store.DefaultDataDir(), "data directory for mnemon database")
34+
rootCmd.PersistentFlags().StringVar(&dataDir, "data-dir", store.DefaultDataDir(), "base data directory")
35+
rootCmd.PersistentFlags().StringVar(&storeName, "store", "", "named memory store (overrides MNEMON_STORE and active file)")
36+
}
37+
38+
// resolveStoreName returns the effective store name.
39+
// Priority: --store flag > MNEMON_STORE env > active file > "default".
40+
func resolveStoreName() string {
41+
if storeName != "" {
42+
return storeName
43+
}
44+
if env := os.Getenv("MNEMON_STORE"); env != "" {
45+
return env
46+
}
47+
return store.ReadActive(dataDir)
3248
}
3349

3450
// openDB is a helper used by subcommands.
3551
func openDB() (*store.DB, error) {
36-
return store.Open(dataDir)
52+
if err := store.MigrateIfNeeded(dataDir); err != nil {
53+
return nil, fmt.Errorf("migrate: %w", err)
54+
}
55+
name := resolveStoreName()
56+
dir := store.StoreDir(dataDir, name)
57+
return store.Open(dir)
3758
}

cmd/setup.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/mnemon-dev/mnemon/internal/setup"
88
"github.com/mnemon-dev/mnemon/internal/setup/assets"
9+
"github.com/mnemon-dev/mnemon/internal/store"
910
"github.com/spf13/cobra"
1011
)
1112

@@ -120,11 +121,33 @@ func runInstallFlow(envs []setup.Environment) error {
120121
}
121122

122123
func installEnv(env *setup.Environment) error {
124+
var err error
123125
switch env.Name {
124126
case "claude-code":
125-
return installClaudeCode(env)
127+
err = installClaudeCode(env)
126128
case "openclaw":
127-
return installOpenClaw(env)
129+
err = installOpenClaw(env)
130+
}
131+
if err != nil {
132+
return err
133+
}
134+
return initDefaultStore()
135+
}
136+
137+
// initDefaultStore migrates a legacy DB if present, then ensures the
138+
// default store exists so the data directory is ready to use.
139+
func initDefaultStore() error {
140+
if err := store.MigrateIfNeeded(dataDir); err != nil {
141+
fmt.Printf(" Warning: migration failed: %v\n", err)
142+
}
143+
if !store.StoreExists(dataDir, store.DefaultStoreName) {
144+
dir := store.StoreDir(dataDir, store.DefaultStoreName)
145+
db, err := store.Open(dir)
146+
if err != nil {
147+
return fmt.Errorf("init default store: %w", err)
148+
}
149+
db.Close()
150+
fmt.Printf(" Initialized default store at %s\n", dir)
128151
}
129152
return nil
130153
}

cmd/store.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/mnemon-dev/mnemon/internal/store"
8+
"github.com/spf13/cobra"
9+
)
10+
11+
var storeCmd = &cobra.Command{
12+
Use: "store",
13+
Short: "Manage memory stores",
14+
Long: "Create, list, switch, and remove isolated memory stores.",
15+
}
16+
17+
// ── list ─────────────────────────────────────────────────────────────
18+
19+
var storeListCmd = &cobra.Command{
20+
Use: "list",
21+
Short: "List all memory stores",
22+
RunE: func(cmd *cobra.Command, args []string) error {
23+
names, err := store.ListStores(dataDir)
24+
if err != nil {
25+
return err
26+
}
27+
if len(names) == 0 {
28+
fmt.Println(" (no stores yet — run 'mnemon store create <name>' or any command to create default)")
29+
return nil
30+
}
31+
active := resolveStoreName()
32+
for _, n := range names {
33+
marker := " "
34+
if n == active {
35+
marker = "* "
36+
}
37+
fmt.Printf("%s%s\n", marker, n)
38+
}
39+
return nil
40+
},
41+
}
42+
43+
// ── create ───────────────────────────────────────────────────────────
44+
45+
var storeCreateCmd = &cobra.Command{
46+
Use: "create <name>",
47+
Short: "Create a new memory store",
48+
Args: cobra.ExactArgs(1),
49+
RunE: func(cmd *cobra.Command, args []string) error {
50+
name := args[0]
51+
if !store.ValidStoreName(name) {
52+
return fmt.Errorf("invalid store name %q: must match [a-zA-Z0-9][a-zA-Z0-9_-]*", name)
53+
}
54+
if store.StoreExists(dataDir, name) {
55+
return fmt.Errorf("store %q already exists", name)
56+
}
57+
dir := store.StoreDir(dataDir, name)
58+
db, err := store.Open(dir)
59+
if err != nil {
60+
return fmt.Errorf("create store: %w", err)
61+
}
62+
db.Close()
63+
fmt.Printf("Created store %q\n", name)
64+
return nil
65+
},
66+
}
67+
68+
// ── set ──────────────────────────────────────────────────────────────
69+
70+
var storeSetCmd = &cobra.Command{
71+
Use: "set <name>",
72+
Short: "Set the default active store",
73+
Args: cobra.ExactArgs(1),
74+
RunE: func(cmd *cobra.Command, args []string) error {
75+
name := args[0]
76+
if !store.StoreExists(dataDir, name) {
77+
return fmt.Errorf("store %q does not exist (use 'mnemon store create %s' first)", name, name)
78+
}
79+
if err := store.WriteActive(dataDir, name); err != nil {
80+
return fmt.Errorf("write active file: %w", err)
81+
}
82+
fmt.Printf("Active store set to %q\n", name)
83+
return nil
84+
},
85+
}
86+
87+
// ── remove ───────────────────────────────────────────────────────────
88+
89+
var storeRemoveCmd = &cobra.Command{
90+
Use: "remove <name>",
91+
Short: "Remove a memory store and all its data",
92+
Args: cobra.ExactArgs(1),
93+
RunE: func(cmd *cobra.Command, args []string) error {
94+
name := args[0]
95+
if !store.StoreExists(dataDir, name) {
96+
return fmt.Errorf("store %q does not exist", name)
97+
}
98+
active := resolveStoreName()
99+
if name == active {
100+
return fmt.Errorf("cannot remove the active store %q (switch first with 'mnemon store set <other>')", name)
101+
}
102+
dir := store.StoreDir(dataDir, name)
103+
if err := os.RemoveAll(dir); err != nil {
104+
return fmt.Errorf("remove store: %w", err)
105+
}
106+
fmt.Printf("Removed store %q\n", name)
107+
return nil
108+
},
109+
}
110+
111+
func init() {
112+
storeCmd.AddCommand(storeListCmd, storeCreateCmd, storeSetCmd, storeRemoveCmd)
113+
rootCmd.AddCommand(storeCmd)
114+
}

docs/DESIGN.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ The four edge types form the foundation of the MAGMA four-graph model, detailed
194194

195195
### 3.3 Database Schema
196196

197-
All data is stored in a single SQLite file (`~/.mnemon/mnemon.db`), using WAL mode to support concurrent reads:
197+
Each named store has its own SQLite file under `~/.mnemon/data/<store>/mnemon.db`, using WAL mode to support concurrent reads. The default store is `default`; additional stores can be created for data isolation (see [Store Management](../USAGE.md#store-management)).
198198

199199
```sql
200200
-- Memory nodes
@@ -248,7 +248,8 @@ Mnemon's architecture is divided into five layers:
248248
```
249249
mnemon/
250250
├── cmd/ # CLI commands (Cobra)
251-
│ ├── root.go # Root command, global flags
251+
│ ├── root.go # Root command, global flags, store resolution
252+
│ ├── store.go # Store management (list, create, set, remove)
252253
│ ├── remember.go # Store insight + auto-create edges
253254
│ ├── recall.go # Retrieval (smart graph-enhanced, default)
254255
│ ├── diff.go # Standalone dedup/conflict check
@@ -278,7 +279,7 @@ mnemon/
278279
│ │ ├── intent.go # Intent detection
279280
│ │ └── keyword.go # Token-level keyword scoring
280281
│ ├── store/ # SQLite persistence
281-
│ │ ├── db.go # Database initialization, transactions
282+
│ │ ├── db.go # Database initialization, transactions, store management
282283
│ │ ├── node.go # Insight CRUD, lifecycle
283284
│ │ ├── edge.go # Edge CRUD
284285
│ │ └── oplog.go # Operation log
@@ -943,11 +944,11 @@ For CLIs without hook support, merge the recall/remember guidance into the corre
943944

944945
### Why SQLite WAL Instead of an Embedded Graph Database?
945946

946-
- **Single-file deployment**: `~/.mnemon/mnemon.db` — one file is everything
947+
- **Single-file deployment**: one `.db` file per store — easy to manage and backup
947948
- **ACID transactions**: Atomicity guarantee for the remember pipeline
948949
- **WAL concurrency**: Supports simultaneous hook reads and CLI writes
949950
- **Zero external dependencies**: No Redis/Neo4j/Qdrant required
950-
- **Easy backup**: Just copy one file
951+
- **Store isolation**: Named stores (`~/.mnemon/data/<name>/mnemon.db`) provide lightweight data isolation via `MNEMON_STORE` env var
951952

952953
### Why Beam Search Instead of Full BFS?
953954

docs/USAGE.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,33 @@ mnemon gc --threshold 0.5
4545
mnemon gc --keep <id>
4646
```
4747

48+
### Store Management
49+
50+
Mnemon supports named stores for data isolation. Each store has its own independent database.
51+
52+
```bash
53+
# List all stores (* marks the active one)
54+
mnemon store list
55+
56+
# Create a new store
57+
mnemon store create work
58+
59+
# Switch the default active store
60+
mnemon store set work
61+
62+
# Remove a store (cannot remove the active store)
63+
mnemon store remove old-project
64+
```
65+
66+
**Store resolution priority** (highest to lowest):
67+
68+
1. `--store <name>` CLI flag
69+
2. `MNEMON_STORE` environment variable
70+
3. `~/.mnemon/active` file
71+
4. Falls back to `"default"`
72+
73+
Different agents or processes can use different stores via the `MNEMON_STORE` environment variable — no global state contention. Legacy databases (`~/.mnemon/mnemon.db`) are automatically migrated to `~/.mnemon/data/default/` on first run.
74+
4875
### Observability
4976

5077
```bash

docs/zh/DESIGN.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ Edge 连接两个 insight,代表它们之间的关系。每条边包含:
194194

195195
### 3.3 数据库模式
196196

197-
所有数据存储在单个 SQLite 文件中(`~/.mnemon/mnemon.db`,使用 WAL 模式支持并发读取
197+
每个命名记忆体拥有独立的 SQLite 文件,位于 `~/.mnemon/data/<store>/mnemon.db`,使用 WAL 模式支持并发读取。默认记忆体为 `default`;可创建额外记忆体进行数据隔离(参见[记忆体管理](USAGE.md#记忆体管理))。
198198

199199
```sql
200200
-- 记忆节点
@@ -248,7 +248,8 @@ Mnemon 的架构分为五层:
248248
```
249249
mnemon/
250250
├── cmd/ # CLI 命令(Cobra)
251-
│ ├── root.go # 根命令,全局 flags
251+
│ ├── root.go # 根命令,全局 flags,记忆体解析
252+
│ ├── store.go # 记忆体管理(list、create、set、remove)
252253
│ ├── remember.go # 存储 insight + 自动建边
253254
│ ├── recall.go # 检索(智能图增强,默认)
254255
│ ├── diff.go # 独立去重/冲突检查
@@ -278,7 +279,7 @@ mnemon/
278279
│ │ ├── intent.go # 意图检测
279280
│ │ └── keyword.go # Token 级关键词评分
280281
│ ├── store/ # SQLite 持久化
281-
│ │ ├── db.go # 数据库初始化、事务
282+
│ │ ├── db.go # 数据库初始化、事务、记忆体管理
282283
│ │ ├── node.go # Insight CRUD、生命周期
283284
│ │ ├── edge.go # Edge CRUD
284285
│ │ └── oplog.go # 操作日志
@@ -943,11 +944,11 @@ Prime 钩子始终安装。Remind、Nudge、Compact 钩子可选(Remind 和 Nu
943944

944945
### 为什么选择 SQLite WAL 而非嵌入式图数据库?
945946

946-
- **单文件部署**`~/.mnemon/mnemon.db` 一个文件就是全部
947+
- **单文件部署**每个记忆体一个 `.db` 文件 — 易于管理和备份
947948
- **ACID 事务**:remember 管线的原子性保证
948949
- **WAL 并发**:支持 hook 读取和 CLI 写入同时进行
949950
- **零外部依赖**:不需要 Redis/Neo4j/Qdrant
950-
- **方便备份**复制一个文件即可
951+
- **记忆体隔离**命名记忆体(`~/.mnemon/data/<name>/mnemon.db`)通过 `MNEMON_STORE` 环境变量提供轻量数据隔离
951952

952953
### 为什么用 Beam Search 而非完整 BFS?
953954

0 commit comments

Comments
 (0)