trusty-memory 0.26.2

MCP server (stdio + Unix socket) for trusty-memory
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
# trusty-memory

[![crates.io](https://img.shields.io/crates/v/trusty-memory.svg)](https://crates.io/crates/trusty-memory)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

Memory palace MCP server (stdio + Unix socket) backed by `hnsw_rs` (HNSW)
vector store, `redb` metadata and knowledge-graph stores, and `fastembed`
embeddings. Stores and retrieves natural-language memories organized into
named "palaces" (namespaces), with an optional knowledge-graph layer for
structured triples.

Claude Code and Codex integration uses `trusty-memory serve` — a direct
stdio JSON-RPC MCP server that forwards every request to the running daemon
over its Unix domain socket and returns daemon responses verbatim.
`serve --stdio` is the same server: the flag selects nothing since #5267, and
`trusty-memory setup` rewrites a registration still carrying it (#5265).

A DEPRECATED `trusty-memory-mcp-bridge` shim binary is also installed by
`cargo install trusty-memory` so that existing `.mcp.json` configs that still
reference the old bridge name keep working without per-project changes.
To update your config, manually set the `trusty-memory` entry to
`"command": "trusty-memory", "args": ["serve"]` in your `.mcp.json`
or `~/.claude/mcp.json`.

Integrates with Claude Code and any other MCP-aware client as a first-class
long-term memory backend.

## System Requirements

- **RAM**: 512 MB minimum; 1 GB+ recommended (ONNX embedding model loads ~22 MB,
  hnsw_rs index scales with corpus size)
- **Disk**: ~100 MB for the model cache on first run
  (`~/Library/Application Support/trusty-memory/` on macOS,
  `~/.local/share/trusty-memory/` on Linux)
- **Rust**: 1.88+ (if building from source)

## Installation

### From GitHub Releases (recommended for binary users)

Prebuilt binaries are available for macOS (Apple Silicon) and Linux (x86_64).

1. Open [GitHub Releases]https://github.com/bobmatnyc/trusty-tools/releases,
   choose the newest `trusty-memory-v<version>` release, and download the
   archive for your platform.

2. Extract and install:
   ```bash
   tar xzf trusty-memory-*.tar.gz
   chmod +x trusty-memory
   sudo mv trusty-memory /usr/local/bin/    # or ~/.local/bin/ if you prefer user install
   ```

3. Verify the installation:
   ```bash
   trusty-memory --version
   ```

### From Source with Cargo

Requires Rust 1.94 or later ([install Rust](https://rustup.rs/)).

```bash
cargo install --git https://github.com/bobmatnyc/trusty-tools trusty-memory --locked
```

This builds from the latest commit on `main` and installs the binary to `~/.cargo/bin/`. Make sure `~/.cargo/bin/` is on your PATH.

Installing `trusty-memory` produces two binaries in one command: `trusty-memory` and `trusty-memory-mcp-bridge` (deprecated shim — forwards to `serve --stdio`). `trusty-console` was de-bundled in #1318 and `trusty-bm25-daemon` in #5329 — BM25 now runs inside the memory daemon.

To install a specific release, replace `<version>` with its tag version:
```bash
cargo install --git https://github.com/bobmatnyc/trusty-tools --tag trusty-memory-vx.y.z trusty-memory --locked
```

### With Homebrew (recommended)

```bash
brew tap bobmatnyc/trusty
brew install trusty-memory
```

Or install directly without tapping:

```bash
brew install bobmatnyc/trusty/trusty-memory
```

Homebrew provides:
- Automatic updates via `brew upgrade trusty-memory`
- Standard macOS / Linux PATH integration
- Easy dependency management

### Prerequisites & Special Cases

#### Prerequisites

None — the daemon is self-contained and requires no external databases or configuration files to start.

#### Optional: OpenRouter API Key

The daemon reads `OPENROUTER_API_KEY` for two features: the `memory.chat` MCP method, and the dream cycle's semantic-consolidation summarization pass. There is no UI to enter a key into — set it in the environment before starting the daemon.

```bash
export OPENROUTER_API_KEY=sk-or-v1-...
trusty-memory              # Start the daemon with chat and consolidation summaries enabled
```

Both features are optional; the daemon fully functions without a key.

#### Note: no embedded UI

This crate has no embedded UI and no HTTP listener — it serves one Unix
domain socket (`transport::uds`). The admin dashboard moved to the separate
`trusty-console` crate; see [Web UI](#web-ui) below.

### Verify Installation

All installations can be verified by running:

```bash
trusty-memory --version
```

Expected output: the semantic version of the installed binary (e.g., `trusty-memory 0.15.0`).

## Quick Start

### Start the daemon

```bash
trusty-memory start
```

`start` is the daemon verb: it spawns a detached `serve --foreground` and
returns once the socket answers `memory.health`. Bare `trusty-memory serve`
speaks MCP over stdio (matching `trusty-search serve`), so use `start` — not
`serve` — when you want a background daemon. The daemon binds a derived Unix
domain socket (`trusty_common::daemon_socket_path`) — there is no port and no
discovery file to publish. Pass `--foreground` to keep the daemon inline (used
by launchd / systemd / Docker).

### Find the daemon's socket

```bash
trusty-memory port               # socket path: /…/trusty-memory.sock
trusty-memory port --addr        # same path, for a client that dials it
trusty-memory port --json        # {"socket":"/…/trusty-memory.sock","serving":true}
```

Exits non-zero with a message on stderr when no daemon is running, so shell
substitution fails cleanly.

### Dashboard

`trusty-memory start` serves no HTTP and no embedded UI — one Unix domain
socket only. The dashboard lives in the separate `trusty-console` crate,
which reads the daemon over that socket; see [Web UI](#web-ui) below. For the
tool surface (MCP and the equivalent socket methods), see
[Available MCP Tools](#available-mcp-tools).

### Bind to a named palace

When all tool calls should default to one palace namespace, use `--palace`:

```bash
trusty-memory start          # daemon
trusty-memory serve --palace my-project   # MCP stdio bound to one palace
```

With a default set, the `palace` argument becomes optional in every MCP tool
call.

## Claude Code Integration

Run `trusty-memory setup` once — it installs the launchd LaunchAgent
(macOS), pre-warms the embedder cache, and patches every Claude settings
file it finds with the canonical MCP server entry.

### Canonical MCP config (0.15.3+)

The recommended entry in `.mcp.json` or `~/.claude/mcp.json` is:

```json
{
  "mcpServers": {
    "trusty-memory": {
      "command": "trusty-memory",
      "args": ["serve"],
      "env": {}
    }
  }
}
```

Codex reads its own file. `trusty-memory setup` writes the same entrypoint into
`~/.codex/config.toml` as `[mcp_servers.trusty-memory]` with
`command = "trusty-memory"` and `args = ["serve"]`, repairing an empty, joined,
legacy `--stdio`, or nested-JSON-string vector without touching any other table
or comment (#5265).

`trusty-memory serve` is a pure daemon-bridge proxy: it ensures the daemon is
running (auto-starting it if absent), then forwards every JSON-RPC request
over the daemon's Unix domain socket and returns the response verbatim. The
stdio process never opens the redb write-lock directly, so it co-exists
safely with the running daemon.

If you are switching from the legacy `kuzu-memory` server, run
`trusty-memory migrate kuzu-memory` to rewrite all Claude settings files
automatically (see [Migrating from kuzu-memory](#from-kuzu-memory-mcp-config-issue-278) below).

### Compatibility shim (for existing installations)

If your `.mcp.json` still references `trusty-memory-mcp-bridge`, the shim
binary (installed alongside `trusty-memory` since 0.15.3) forwards to
`serve --stdio` automatically. You will see a one-line deprecation warning
on stderr (which Claude Code ignores). To update your config manually, set
the `trusty-memory` entry to:

```json
{
  "mcpServers": {
    "trusty-memory": {
      "command": "trusty-memory",
      "args": ["serve"]
    }
  }
}
```

There is no automated command to rewrite `trusty-memory-mcp-bridge` entries
(unlike the kuzu-memory migration). The shim will be removed in a future
minor version.

Claude Code auto-discovers `.mcp.json` on project open. The daemon must be
running (started either by `trusty-memory setup`'s LaunchAgent or by
`trusty-memory start`).

## Available MCP Tools

All tools are exposed via the MCP protocol (over the `serve --stdio` path).
The `palace` argument is required unless
the server was started with `--palace <name>`, which makes it optional
everywhere.

Chat-session tools (`chat_session_*`, `chat_turn_append`) back a redb-backed,
per-palace conversation store: turns are stored verbatim and bypass the
`memory_remember` signal/noise and dedup gates.

<!-- BEGIN GENERATED: mcp-tools -->
The MCP server registers **52 tools**. Authoritative source: `trusty_memory::tools::tool_definitions` —
this table is generated from it, not maintained by hand.

| Tool | Arguments | Summary |
|---|---|---|
| `add_alias` | `palace`, `short`, `full`, `extra?` | Add a short→full alias (e.g. tga → trusty-git-analytics) to the prompt-facts surface. |
| `chat_asset_capabilities` | — | Read supported durable chat attachment contract. |
| `chat_asset_get` | `palace`, `session_id`, `asset_id` | Read an image owned by the supplied palace and session. |
| `chat_asset_put` | `palace`, `session_id`, `name`, `mime_type`, `data_base64` | Store a bounded image in an existing palace chat session; returns generated asset_id. |
| `chat_session_add_turn` | `palace`, `session_id`, `role`, `content`, `attachments?` | Append a message (prompt or response) to a chat session's history. |
| `chat_session_create` | `palace`, `session_id?`, `title?` | Create a new chat session in a palace (spec-001 chat-session manager). |
| `chat_session_delete` | `palace`, `session_id` | Delete a chat session (and its full history) from a palace. |
| `chat_session_get` | `session_id`, `palace?` | Retrieve a full chat session: metadata plus every turn in chronological order. |
| `chat_session_list` | `limit?`, `offset?`, `palace?` | List chat sessions in a palace as paginated metadata (id, title, timestamps, message_count) ordered most-recently-updated first. |
| `chat_session_recall` | `session_id`, `full?`, `max_bytes?`, `palace?` | Retrieve a full chat session with all turns in order (alias for chat_session_get, preferred name for agent-facing recall). |
| `chat_turn_append` | `palace`, `session_id`, `prompt`, `response`, `attachments?` | Append a prompt/response PAIR to a chat session as two consecutive messages (user role then assistant role). |
| `console_metrics` | — | Return a ConsoleMetricsReport with palace aggregate statistics (palace_count, counted_palace_count, cached_palace_count, total_drawers,… |
| `discover_aliases` | `palace`, `project_root?` | Auto-discover project aliases by scanning Cargo workspace members, binary names, first-letter abbreviations, and the git remote. |
| `dream_consolidate_room` | `palace`, `max_age_days?`, `room?` | Trigger LLM-driven semantic consolidation for one room (or all rooms) of a palace, on demand and synchronously (spec-001). |
| `get_prompt_context` | `query?` | Fetch the current project context (aliases, conventions, facts, shorthands) from the memory palace as a Markdown block ready to drop into… |
| `kg_assert` | `palace`, `subject`, `predicate`, `object`, `confidence?`, `provenance?` | Assert a fact in the temporal knowledge graph. |
| `kg_bootstrap` | `palace?`, `project_path?` | Seed the knowledge graph from well-known project files (Cargo.toml, package.json, pyproject.toml, go.mod, CLAUDE.md, .git/config). |
| `kg_gaps` | `palace?` | List knowledge gaps detected in the memory palace graph. |
| `kg_list_subjects` | `limit?`, `palace?`, `with_counts?` | List the subjects this palace's knowledge graph actually holds, ordered by subject. |
| `kg_query` | `subject`, `full?`, `max_bytes?`, `palace?` | Query active knowledge-graph triples for a subject. |
| `kg_retract_triple` | `palace`, `subject`, `predicate`, `object` | Retract one fact from the temporal knowledge graph — the inverse of kg_assert. |
| `list_prompt_facts` | `full?`, `max_bytes?` | List every active prompt-fact triple (aliases, conventions, facts, shorthands) across all palaces. |
| `memory_forget` | `palace`, `drawer_id` | Delete a drawer from a palace by its UUID. |
| `memory_list` | `full?`, `limit?`, `max_bytes?`, `palace?`, `room?`, `tag?`, `wing?` | List drawers in a palace, optionally filtered by wing, room type, or tag. |
| `memory_note` | `palace`, `content`, `context?`, `cwd?`, `expires_at?`, `fact_key?`, `room?`, `tags?`, `workstream?` | Curated shortcut for short, high-signal facts ("User prefers snake_case", "Deploy target is prod-east"). |
| `memory_recall` | `query`, `full?`, `include_creator_tags?`, `max_bytes?`, `min_score?`, `palace?`, `room?`, `top_k?`, `wing?` | Recall memories using L0+L1+L2 progressive retrieval. |
| `memory_recall_all` | `q`, `deep?`, `full?`, `include_creator_tags?`, `max_bytes?`, `top_k?` | Semantic search across ALL palaces simultaneously. |
| `memory_recall_deep` | `query`, `full?`, `include_creator_tags?`, `max_bytes?`, `min_score?`, `palace?`, `room?`, `top_k?` | Deep recall using L3 full HNSW search. |
| `memory_remember` | `palace`, `text`, `allow_secret_like?`, `context?`, `cwd?`, `expires_at?`, `fact_key?`, `force?`, `room?`, `tags?`, `wing?`, `workstream?` | Store a memory (drawer) in a palace room. |
| `memory_send_message` | `to_palace`, `purpose`, `content`, `cwd?`, `from_palace?`, `workstream?` | Send an inter-project message (issue #99). |
| `palace_compact` | `palace` | Remove orphaned vector index entries (vectors with no matching drawer row). |
| `palace_create` | `name`, `cwd?`, `description?`, `force?` | Create a new memory palace. |
| `palace_delete` | `palace_id`, `force?` | Delete an entire memory palace, including its drawers, vectors, and knowledge graph. |
| `palace_dream` | `palace`, `compact?`, `dry_run?`, `max_age_days?`, `room?` | On-demand LLM-driven consolidation for a palace (issue #1721). |
| `palace_embed_sweep` | — | #5000 / #4786: vector coverage for EVERY palace on disk, uncapped. |
| `palace_info` | `palace?` | Get metadata and stats for a single palace. |
| `palace_list` | — | List all palaces on this machine. |
| `palace_reembed` | `palace`, `dry_run?`, `limit?` | #4906: report drawers that have no vector (durable but unfindable), and optionally re-embed them. |
| `palace_unalias` | `palace`, `dry_run?` | #5005: free drawers whose vector was destroyed by an id collision (`palace_reembed` reports these as `aliased`), so a re-embed can repair… |
| `palace_update` | `palace_id`, `name` | Update the display name of an existing palace. |
| `palace_verify_embedded` | `drawer_ids`, `palace?` | #5000: answer whether YOUR OWN drawer ids are vector-findable. |
| `remove_prompt_fact` | `subject`, `predicate` | Retract the active triple for a (subject, predicate) pair from the prompt-facts surface. |
| `room_create` | `palace`, `label`, `description?`, `wing?` | Create a room in a palace, or return the existing one (ADR-0027). |
| `room_list` | `palace?`, `wing?` | List every room registered in a palace (ADR-0027). |
| `room_rename` | `palace`, `room`, `new_label` | Rename a room (ADR-0027). |
| `task_add` | `palace`, `content`, `room?`, `tags?` | Create a Task drawer in a palace (spec-001 issue #1722). |
| `task_complete` | `palace`, `drawer_id` | Mark a Task drawer as completed by setting its completed_at timestamp (spec-001 issue #1722). |
| `task_list` | `include_completed?`, `palace?` | List Task drawers in a palace (spec-001 issue #1722). |
| `upgrade` | `check?`, `confirm?` | Check for or install a new version of trusty-memory (issue #537). |
| `wing_create` | `palace`, `label` | Create a wing (scope) in a palace, or return the existing one with that label (ADR-0027). |
| `wing_list` | `palace?` | List the wings of a palace (ADR-0027). |
| `wing_rename` | `palace`, `wing`, `new_label` | Rename a wing (ADR-0027). |
<!-- END GENERATED: mcp-tools -->

Global daemon statistics (total drawers, vectors, KG triples) are available
over the socket as `memory.status` (`StatusPayload` in
`src/service/types.rs`, bound in `src/transport/uds.rs`); there is no
corresponding MCP tool, which is why the generated roster above does not
list one.

### Task drawers (protected memory)

`DrawerType::Task` is a drawer classification for goals, milestones, and
checkpoints that an application must re-derive across sessions. Task drawers are
**protected from the dream cycle**: they are never evicted (content-prune,
dedup-merge, or age/importance prune) and never consolidated into summaries,
regardless of age or importance. An optional `completed_at` timestamp marks a
task done — this makes it eligible for *manual* cleanup but never triggers
automatic eviction. Store a Task drawer by classifying a write as `Task`; it
will survive every subsequent dream cycle until explicitly deleted.

## Dream Cycle: Semantic Consolidation (issue #87)

The `palace_dream` / `dream_consolidate_room` tools (and the background idle-dream timer) now include an
**inference-backed semantic consolidation phase** after the existing NLP passes
(dedup, prune, closet-refresh):

### What it does

1. Groups palace drawers into batches of up to 8 (configurable via `DreamConfig`).
2. Sends each batch to an LLM backend (OpenRouter or local Ollama) with a
   structured prompt asking for three actions:
   - **Alias** — two terms refer to the same concept (e.g. `"ts"` → `"trusty-search"`).
     A `superseded_by` KG triple is written to link the alias to its canonical form.
   - **Merge** — a cluster of overlapping drawers is collapsed into one canonical
     drawer. The original drawers are preserved (additive-only policy); a
     `superseded_by` KG triple is written from each original to the new canonical
     drawer so lineage is traceable.
   - **Flag** — a drawer contradicts another and should be reviewed by a human.
     Flagged drawers are logged at `WARN` level but not deleted.
3. Each batch response is cached by a SHA-256 key over the drawer IDs + content,
   so repeated dream cycles do not re-spend LLM tokens on stable content.
4. A per-cycle call budget (`max_calls_per_cycle`, default 20) prevents runaway
   LLM spend on large palaces.

### Configuration

Semantic consolidation is enabled when at least one inference backend is available:

| Priority | When used |
|---|---|
| OpenRouter | `OPENROUTER_API_KEY` env var is set, or `DreamConfig.openrouter_api_key` is non-empty |
| Ollama (local) | `DreamConfig.local_model_enabled = true` AND no OpenRouter key is present |
| Disabled (no-op) | Neither backend is available — the phase is silently skipped |

The consolidation model defaults to `anthropic/claude-haiku-4-5` for low cost.
Override via `DreamConfig.semantic.model`.

```toml
# ~/.trusty-memory/config.toml — set these to enable semantic consolidation
[openrouter]
api_key = "sk-or-v1-..."   # enables semantic consolidation via OpenRouter  # pragma: allowlist secret

[local_model]
enabled = false             # set true + unset api_key to use Ollama instead
base_url = "http://127.0.0.1:11434"
model = "llama3"
```

### Testing

All consolidation tests use `MockInference` — no real API calls are made during
`cargo test`. Live-inference tests are marked `#[ignore]`:

```bash
# Unit + integration tests (no network)
cargo test -p trusty-common --features memory-core -- semantic_consolidation

# Live-network tests (requires OPENROUTER_API_KEY)
cargo test -p trusty-common --features memory-core -- --include-ignored semantic_consolidation
```

### Inter-project messaging (issue #99)

| Tool | Arguments | Description |
|---|---|---|
| `memory_send_message` | `to_palace, purpose, content, from_palace?` | Deliver a message to another palace's inbox. |

Plus two CLI subcommands:

- `trusty-memory send-message --to <palace> --purpose <p> --content <text> [--from <palace>]`
  — non-MCP entry point. Calls the daemon's `memory.message_send` socket method
  (`src/commands/send_message.rs`).
- `trusty-memory inbox-check [--palace <id>]` — installed as a Claude Code
  `SessionStart` hook by `setup`. Reads unread messages from the cwd-derived
  palace, prints them to stdout (Claude Code injects stdout as session
  context), and atomically marks them read.

#### Design

A message is a **drawer in the recipient's palace** carrying a namespaced
tag envelope. No new schema, no new database — just convention:

| Tag | Example | Meaning |
|---|---|---|
| `msg:v1` | (literal) | Marker tag for the v1 envelope. |
| `msg:from=<palace>` | `msg:from=trusty-tools` | Sender palace id. |
| `msg:to=<palace>` | `msg:to=claude-mpm` | Recipient palace id (audit). |
| `msg:purpose=<text>` | `msg:purpose=task` | Free-text purpose / category. |
| `msg:sent_at=<rfc3339>` | `msg:sent_at=2026-05-25T12:34:56+00:00` | UTC send timestamp. |
| `msg:read=<bool>` | `msg:read=false` | Receiver-flipped read flag. |

#### Addressing

Sender and recipient palaces are addressed by **repo slug**. The slug is
derived from the working directory by:

1. Take the basename of `git rev-parse --show-toplevel` (or cwd, when not in
   a git checkout).
2. Strip a trailing `.git` suffix if present.
3. Lowercase.
4. Replace every run of whitespace or `_` with a single `-`.
5. Strip every character outside `[a-z0-9-]`.
6. Collapse consecutive `-` and trim leading/trailing `-`.

Examples (all resolve to `trusty-tools`):
`/Users/bob/Projects/trusty-tools`,
`/Users/bob/Projects/Trusty_Tools`,
`/Users/bob/Projects/trusty tools`,
`/Users/bob/Projects/.trusty-tools.git`.

No central registry; sender and receiver agree on the slug out of band.

#### Delivery

The receiver's `trusty-memory setup` installs `trusty-memory inbox-check` as
a `SessionStart` hook in every Claude Code settings file it finds (alongside
the existing `UserPromptSubmit` `prompt-context` hook). On every new Claude
Code session, the hook:

1. Resolves the receiver palace slug from cwd.
2. Fetches unread messages via the daemon's `memory.messages_list` socket
   method (`src/commands/inbox_check.rs`).
3. Prints each as a Markdown block to stdout — Claude Code injects stdout as
   session context.
4. Atomically marks each delivered message read via `memory.message_mark_read`.

The mark-read step uses an in-memory compare-and-swap on the palace's
drawer table so two concurrent sessions opening at once cannot
double-deliver: exactly one observes `read=false` and flips the flag, the
other returns `false` and emits nothing.

Every failure path in `inbox-check` degrades to exit 0 with empty stdout
so a missing or slow daemon never blocks Claude Code session start.

#### Migration from `claude-mpm` `/mpm-message`

This primitive replaces the Python `/mpm-message` skill in `claude-mpm`
(which wrote to `~/.claude-mpm/messaging.db` via a process-local SQLite
file). The companion ticket in `claude-mpm` is `#557`; data migration is
out of scope here.

## Palace as Project

(Issue #88) Palace names are anchored to the project they belong to.

### One palace per project

When you create a new palace, trusty-memory walks upward from the current
working directory looking for project markers (`.git`, `Cargo.toml`,
`pyproject.toml`, `package.json`, `go.mod`) and derives a **canonical slug**
from the project root's directory name:

```
/Users/bob/Projects/trusty-tools  →  trusty-tools
/Users/bob/Projects/My_App!       →  my-app
/Users/bob/Projects/my project    →  my-project
```

New palaces must be named with the derived slug:

```bash
# Inside /Users/bob/Projects/trusty-tools — this succeeds
palace_create("trusty-tools")

# Any other name fails with a descriptive error:
# "palace name 'notes' does not match the project slug 'trusty-tools'.
#  Either use 'trusty-tools' or use 'personal' for non-project memories."
palace_create("notes")
```

### The `personal` palace

When operating outside any project directory (no `.git` / `Cargo.toml` /
etc. found anywhere above CWD), only the special palace name `personal` is
allowed. This is the "no project, no problem" escape hatch for global notes,
one-off sessions, and personal task lists.

```bash
# From any directory without a project root:
palace_create("personal")  # always succeeds
palace_create("my-notes")  # fails — no project root detected
```

### Existing palaces are grandfathered

The enforcement applies **only to new palace creation**. Every palace created
before this feature was added continues to work without any migration.
Existing palaces remain read-write and are never auto-renamed.

### `doctor --fix-palaces`

The `doctor --fix-palaces` subcommand audits existing palaces and reports
which ones are orphaned (name does not correspond to a detectable project
directory on disk):

```bash
# Dry-run audit (no filesystem changes):
trusty-memory doctor --fix-palaces

# Include rename suggestions for orphaned palaces (still read-only):
trusty-memory doctor --fix-palaces --fix
```

Output example:

```
✅  trusty-tools     — project palace ok
⚠️   old-project     — orphaned (no matching project directory found on disk)
   → rename suggested: old-project → personal
❌  broken-palace    — empty (no palace.json; directory may be a leftover)

· palace audit: 1 ok, 1 orphaned, 1 empty.
```

The `--fix` flag prints rename suggestions but **does not mutate the
filesystem**. Actual renaming (merging orphaned data into `personal`) is
planned for a future release. The `doctor` audit is purely advisory for
existing palaces.

## Web UI

trusty-memory has no HTTP listener or embedded UI of its own — the daemon
serves one Unix domain socket ([#6286](https://github.com/bobmatnyc/trusty-tools/issues/6286)).
The admin dashboard moved to the separate `trusty-console` crate
([#6155](https://github.com/bobmatnyc/trusty-tools/issues/6155)), which reads
the daemon over that socket and serves the dashboard at `/tools/memory/`.
Install it separately: `cargo install trusty-console`.

The dashboard provides:
- Real-time palace overview (drawer counts, vector counts, KG triple counts)
- Live event stream (palace created, drawer added/deleted, dream completed)
- Manual dream (consolidation) trigger
- Palace-scoped memory browsing

## Configuration

### Environment variables

| Variable | Default | Description |
|---|---|---|
| `RUST_LOG` | `warn` | Tracing filter. E.g. `RUST_LOG=info` or `RUST_LOG=trusty_memory=debug`. |
| `OPENROUTER_API_KEY` | — | Enables chat completions via OpenRouter for the `memory.chat` MCP method and dream-cycle summarization. |
| `TRUSTY_DATA_DIR_OVERRIDE` | — | Override the data directory (intended for tests). |

### Config file

`~/.trusty-memory/config.toml` (created on first run if absent):

```toml
[openrouter]
api_key = ""
model = "anthropic/claude-3.5-sonnet"

[local_model]
enabled = false
base_url = "http://127.0.0.1:11434"
model = "llama3"
```

### Data directory

Memories and vector indexes persist under the OS-standard data directory:

- **macOS**: `~/Library/Application Support/trusty-memory/<palace-id>/`
- **Linux**: `~/.local/share/trusty-memory/<palace-id>/`

Each palace directory contains:
- `kg.redb` — redb store for drawer metadata and knowledge-graph triples
- `index.usearch` — HNSW vector index (`hnsw_rs`, approximate nearest-neighbour)
- `recall.redb` — recall analytics log (redb; migrated from `recall.db` on first open)
- `l1_cache.json` — top-15 drawers by importance (L1 hot cache, rebuilt at each write)
- `palace.json` — palace metadata (name, description, created_at)

## Architecture

```
trusty-memory (this crate)          trusty-common `memory-core` feature
  serve/start (Unix socket) ─────►  PalaceRegistry
  serve --stdio (JSON-RPC)  ─────►  HNSW vector index (index.usearch)
  MCP tool surface                  redb metadata + KG (kg.redb)
                                     fastembed (AllMiniLML6V2Q)

Claude Code stdio ◄──JSON-RPC──► `trusty-memory serve --stdio`
                                  ──Unix socket──► trusty-memory daemon
```

The `memory-core` feature of `trusty-common` owns the storage engine: `hnsw_rs` for approximate
nearest-neighbor search, `redb` for drawer metadata and knowledge-graph triples, and
`fastembed` for 384-dim text embeddings. The MCP server (`trusty-memory`) is a
thin protocol layer on top.

This crate has no embedded UI and no HTTP listener
([#6286](https://github.com/bobmatnyc/trusty-tools/issues/6286)):
`trusty_common::uds::server` serves one Unix domain socket, and
`trusty-console` reads it to render the dashboard — see [Web UI](#web-ui).

## Feature Flags

| Flag | Default | Description |
|------|---------|-------------|
| `daemon` | **enabled** | Compiles the socket-serving surface (`transport::uds`, `serve` / `start`). Disable with `default-features = false` when embedding only the in-process MCP tools (e.g. from `trusty-agents`). Named `axum-server` before [#6286]https://github.com/bobmatnyc/trusty-tools/issues/6286 removed the HTTP listener the old name described. |

```toml
# Full daemon build — no change needed (daemon is on by default)
trusty-memory = { workspace = true }

# rlib consumer — omit the socket-serving surface
trusty-memory = { workspace = true, default-features = false }
```

## Migration

### From kuzu-memory (MCP config, issue #278)

If you're switching from the legacy `kuzu-memory` Python MCP server, rewrite
every Claude `mcpServers` config entry in one command:

```bash
# Dry-run: see what would change
trusty-memory migrate kuzu-memory --dry-run

# Apply: rewrite all Claude settings files atomically
trusty-memory migrate kuzu-memory
```

### Knowledge-graph hygiene (issue #278)

Auto-KG extraction skips drawers tagged `cross-project-qa`, `test`, or
`fixture` so synthetic content never pollutes the graph. Drawers deleted via
`memory_forget` cascade-delete their derived triples automatically.

The `kg_retract_triple` MCP tool (`palace`, `subject`, `predicate`, `object`)
closes exactly one active triple — see [Available MCP Tools](#available-mcp-tools).
Every object at a `(subject, predicate)` pair is a separate row, so the object
is what makes the call name one of them; omitting it is rejected rather than
closing every object at the pair.

### From kuzu-memory data (issue #277)

Import entities and relations from a kuzu-memory `store.redb` into a
trusty-memory palace:

```bash
# Dry-run: see what would be imported
trusty-memory migrate kuzu-data \
  --from ~/.open-mpm/memory/store.redb \
  --palace my-palace \
  --dry-run

# Apply the import (idempotent — safe to re-run)
trusty-memory migrate kuzu-data \
  --from ~/.open-mpm/memory/store.redb \
  --palace my-palace

# Cap at 100 entities
trusty-memory migrate kuzu-data \
  --from ~/.open-mpm/memory/store.redb \
  --palace my-palace \
  --limit 100
```

Each kuzu-memory entity becomes one drawer; each relation becomes one KG
triple. Re-running is idempotent: entity IDs are SHA-256-derived UUIDs so
duplicate imports produce the same drawer ID and are silently skipped.

## Development

```bash
# Build and run (background daemon over a Unix socket)
cargo run -p trusty-memory -- serve

# Run inline (foreground, useful for debuggers)
cargo run -p trusty-memory -- serve --foreground

# Tests
cargo test -p trusty-memory
# Storage-engine tests live in trusty-common behind the memory-core feature:
cargo test -p trusty-common --features memory-core

# Check only (faster)
cargo check -p trusty-memory
```

## License

Licensed under the [MIT License](./LICENSE).

## Repository

<https://github.com/bobmatnyc/trusty-tools>