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
//! `tm memory` command group — deterministic palace maintenance.
//!
//! Why (issue #4837): bulk-loading a directory of memory files into a
//! trusty-memory palace is ETL, not reasoning. Doing it through an agent cost
//! 622k tokens for 120 files; this command group is the zero-inference path.
//! What: [`MemoryAction`] — `import`, plus `import-auto-memory` (#7685), the
//! one-way migration of Claude Code's own auto-memory store into the palace,
//! plus `recall` / `remember` / `note` (#8352), the no-MCP palace verbs, and
//! `forget` (#9340), which deletes one drawer by id.
//! Test: `cli_parses_memory_import*` in `memory_import_cli_tests.rs`;
//! `cli_parses_memory_recall*`, `cli_parses_memory_forget` in `tests.rs`.
use std::path::PathBuf;
use clap::Subcommand;
/// `tm memory` actions.
///
/// #8352: `recall`, `remember` and `note` are the FALLBACK path for a session
/// whose `mcp__trusty-memory__*` tools are unavailable. They call the same
/// daemon methods the MCP tools do, over its Unix socket, so nothing in this
/// process ever opens a palace store the daemon is serving (#1078).
#[derive(Debug, Subcommand)]
pub(crate) enum MemoryAction {
/// Recall memories from the palace — the no-MCP `memory_recall` (#8352).
///
/// Use this when the `mcp__trusty-memory__*` tools are unavailable. The
/// palace is the session's own — `TRUSTY_MEMORY_PALACE`, else the
/// committed pin, else the repo slug — unless `--palace` names another.
/// With no palace resolvable at all, trusty-memory answers with an index of
/// the palaces on this host, naming the one to pass next.
Recall {
/// What to recall.
query: String,
/// Palace to search. Defaults to the session's own.
#[arg(long)]
palace: Option<String>,
/// Hits to return. Defaults to trusty-memory's own default.
#[arg(long)]
top_k: Option<u64>,
/// Restrict the semantic layer to one room.
#[arg(long)]
room: Option<String>,
/// Restrict the search to the rooms one wing owns.
#[arg(long, conflicts_with = "room")]
wing: Option<String>,
/// Drop query-scored hits below this relevance floor (0.4 is the
/// recommended value for PM-context recall).
#[arg(long)]
min_score: Option<f64>,
/// Print the machine-readable envelope instead of the human summary.
#[arg(long)]
json: bool,
/// trusty-memory socket path. Defaults to `TRUSTY_MEMORY_SOCKET`, else
/// the derived one.
#[arg(long)]
memory_socket: Option<PathBuf>,
},
/// Store a memory in the palace — the no-MCP `memory_remember` (#8352).
///
/// The daemon's content gates still apply: very short text with no context,
/// auto-capture noise, and secret-shaped content are refused there, and the
/// report says so. Exits 3 when the drawer was stored but its `--fact-key`
/// slot was refused.
Remember {
/// The memory text.
text: String,
/// Palace to write to. Defaults to the session's own.
#[arg(long)]
palace: Option<String>,
/// Room to file it in.
#[arg(long)]
room: Option<String>,
/// Tag to store alongside it; repeat for several.
#[arg(long = "tag")]
tags: Vec<String>,
// #9142: Tier C slot flags, passed through to trusty-memory.
/// ADR-0028 slot, `<domain>:<id>/<aspect>` (`ws:<session>/resume`,
/// `pr:<n>/state`). The next write to the same key supersedes this one.
#[arg(long)]
fact_key: Option<String>,
/// RFC 3339 time the fact stops being current. With `--fact-key`
/// and omitted, trusty-memory applies a 24-hour default.
#[arg(long)]
expires_at: Option<String>,
/// Print the machine-readable envelope instead of the human summary.
#[arg(long)]
json: bool,
/// trusty-memory socket path. Defaults to the derived one.
#[arg(long)]
memory_socket: Option<PathBuf>,
},
/// Store a short curated fact — the no-MCP `memory_note` (#8352).
///
/// The shortcut for high-signal one-liners ("deploy target is prod-east"):
/// stored at importance 1.0 so it surfaces in the palace's essentials.
/// Exits 3 when the drawer was stored but its `--fact-key` slot was refused.
Note {
/// The fact.
content: String,
/// Palace to write to. Defaults to the session's own.
#[arg(long)]
palace: Option<String>,
/// Room to file it in.
#[arg(long)]
room: Option<String>,
/// Tag to store alongside it; repeat for several.
#[arg(long = "tag")]
tags: Vec<String>,
// #9142: Tier C slot flags, passed through to trusty-memory.
/// ADR-0028 slot, `<domain>:<id>/<aspect>` (`ws:<session>/resume`,
/// `pr:<n>/state`). The next write to the same key supersedes this one.
#[arg(long)]
fact_key: Option<String>,
/// RFC 3339 time the fact stops being current. With `--fact-key`
/// and omitted, trusty-memory applies a 24-hour default.
#[arg(long)]
expires_at: Option<String>,
/// Print the machine-readable envelope instead of the human summary.
#[arg(long)]
json: bool,
/// trusty-memory socket path. Defaults to the derived one.
#[arg(long)]
memory_socket: Option<PathBuf>,
},
/// Delete one drawer by id or by slot — the no-MCP `memory_forget` (#9340).
///
/// For cleaning up test or throwaway drawers. Exits non-zero when the
/// daemon is unreachable, and when it reports no drawer with that id in the
/// palace — an unknown id is never reported as deleted. `--fact-key`
/// lists the palace and forgets the slot's one live occupant; zero
/// matches, several, an incomplete listing or a daemon too old to report
/// slots each exit non-zero with nothing deleted.
Forget {
/// UUID of the drawer to delete, as `remember`/`note`/`recall` print it.
// #9340: exactly one of the id and `--fact-key`.
#[arg(required_unless_present = "fact_key", conflicts_with = "fact_key")]
drawer_id: Option<String>,
/// Forget the drawer that holds this ADR-0028 slot now, instead of an id.
#[arg(long)]
fact_key: Option<String>,
/// Palace to delete from. Defaults to the session's own.
#[arg(long)]
palace: Option<String>,
/// Print the machine-readable envelope instead of the human summary.
#[arg(long)]
json: bool,
/// trusty-memory socket path. Defaults to the derived one.
#[arg(long)]
memory_socket: Option<PathBuf>,
},
/// Move a palace to a new id — trusty-memory's `palace_rename` (#9544).
///
/// Drawers, vectors, the knowledge graph and chat sessions move with it.
/// The old id becomes an alias of the new one, so it keeps working.
/// Refuses an alias source, and an existing target unless it is empty and
/// `--replace-empty` is passed (the empty target moves to
/// `<data root>/.trash`). Exits non-zero on any refusal, with the daemon's
/// reason.
Rename {
/// Current palace id.
old: String,
/// New palace id.
new: String,
/// Replace an existing, empty palace at `<NEW>`.
#[arg(long)]
replace_empty: bool,
/// Print the daemon's JSON answer instead of the human summary.
#[arg(long)]
json: bool,
/// trusty-memory socket path. Defaults to the derived one.
#[arg(long)]
memory_socket: Option<PathBuf>,
},
/// Bulk-import a directory of memory `.md` files into a palace.
///
/// Reads every `*.md` file directly inside `<DIR>` (non-recursive), maps
/// its YAML frontmatter onto drawer fields — the `description` leads the
/// stored text, and `name` + `metadata.type` + every `[[wikilink]]` target
/// become tags — and writes it via trusty-memory's JSON-RPC surface.
///
/// Re-running never writes a file twice. A file's own drawer is found by
/// its slug tag, with drawers that merely link to that slug excluded, so
/// the match does not depend on the file's prose: a file whose text has
/// changed since it was imported is still skipped, and the report says its
/// drawer has drifted. `--refresh` (issue #5044) replaces such a drawer
/// with the file's current text and requires every drawer the run names to
/// be retrievable. When several drawers could be the file's own, or the
/// slug tag is shared by more drawers than one lookup returns, the file is
/// reported as failed rather than guessed at.
Import {
/// Directory of memory `.md` files (scanned non-recursively).
dir: PathBuf,
/// Target palace slug (e.g. `trusty-tools`).
#[arg(long)]
palace: String,
/// Parse, derive, and dedup-check without writing anything.
#[arg(long)]
dry_run: bool,
/// Replace a drifted drawer with the file's current text, and fail any
/// file whose drawer is not retrievable — the mode to run immediately
/// before deleting the source files (issue #5044).
#[arg(long)]
refresh: bool,
/// Print the full JSON report instead of the human summary.
#[arg(long)]
json: bool,
/// Store drawers whose prose trips trusty-memory's secret heuristic
/// (a localhost URL, a token-shaped identifier) instead of failing them.
#[arg(long)]
allow_secret_like: bool,
/// trusty-memory socket path. Defaults to the derived one (#6286 —
/// the daemon has no port and publishes no address).
#[arg(long)]
memory_socket: Option<std::path::PathBuf>,
},
/// Migrate Claude Code's own auto-memory store into the project's palace.
///
/// trusty-memory is the memory; Claude Code's auto memory is the fallback
/// for when it is down (owner ruling 2026-09-12). This command is what makes
/// emptying that fallback safe: it reads every fact file under
/// `<claude-config>/projects/<slug>/memory/`, stores it as a drawer tagged
/// `migrated-from-auto-memory` plus `name:<name>` and `type:<type>`, moves
/// the file into `memory.archived-<YYYYMMDD>/`, and only then empties
/// `MEMORY.md` — archiving a copy of it first.
///
/// Nothing is deleted, and a fact that fails to store is left exactly where
/// it was, with the index untouched, so a re-run picks up the remainder.
/// Re-running after a clean migration finds nothing and writes nothing.
ImportAutoMemory {
/// Project whose auto-memory store is migrated. Defaults to the cwd.
#[arg(long)]
project: Option<PathBuf>,
/// Target palace slug. Defaults to the slug this project's sessions pin.
#[arg(long)]
palace: Option<String>,
/// Print the full JSON report instead of the human summary.
#[arg(long)]
json: bool,
/// trusty-memory socket path. Defaults to the derived one.
#[arg(long)]
memory_socket: Option<PathBuf>,
},
}