@empirical-gauzza/cli 1.0.26 → 1.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -63
- package/bin/empirical.js +7 -1
- package/lib/alertBanner.js +44 -0
- package/lib/alertCommands.js +62 -0
- package/lib/constants.js +45 -1
- package/lib/doc.js +1 -1
- package/lib/env.js +49 -49
- package/lib/http.js +14 -1
- package/lib/installers/coreMemoryBlock.js +5 -0
- package/lib/installers/detect.js +47 -47
- package/lib/installers/hooks.js +1095 -1075
- package/lib/installers/skill-variants/SKILL.both.md +36 -0
- package/lib/installers/skill-variants/SKILL.cli.md +33 -0
- package/lib/installers/skill-variants/SKILL.mcp.md +34 -0
- package/lib/memoryCommands.js +111 -16
- package/lib/oauthClient.js +222 -222
- package/lib/policyCommands.js +144 -144
- package/lib/workspaceContext.js +47 -47
- package/package.json +1 -1
|
@@ -48,6 +48,8 @@ If you are not sure whether a task qualifies, treat it as if it does.
|
|
|
48
48
|
Add `--category` or `--tags` when known. Omit `--pretty` for routine recall because graph-rich
|
|
49
49
|
results can be large. A slow response alone is API latency, not an outage; report an outage
|
|
50
50
|
only when the CLI returns an actual error, and use `empirical doctor` to verify connectivity.
|
|
51
|
+
When the user asks for every record of a topic, add `--path list`; when they ask what a
|
|
52
|
+
memory connects to, add `--path graph`.
|
|
51
53
|
|
|
52
54
|
3. Scope recall to the project you are in. Derive a project tag from the repository name
|
|
53
55
|
(`git rev-parse --show-toplevel`, then the folder name) or, outside a repo, the working
|
|
@@ -61,6 +63,21 @@ If you are not sure whether a task qualifies, treat it as if it does.
|
|
|
61
63
|
doc memory`) over a match query. For exhaustive topic retrieval, use the topic as a tag and
|
|
62
64
|
follow pagination.
|
|
63
65
|
|
|
66
|
+
5. A filtered query (category, tags, nodeType) that returns nothing is not proof the memory
|
|
67
|
+
doesn't exist — it only proves nothing matched that exact filter combination. A memory's
|
|
68
|
+
real category or tag convention often does not match how you or the user would naturally
|
|
69
|
+
describe it in conversation (e.g. described as "an idea" but stored under a different
|
|
70
|
+
category; tagged with a project's short name where you tried its full one). Before
|
|
71
|
+
concluding a memory doesn't exist, retry at least once with the filter relaxed or removed —
|
|
72
|
+
category dropped, tags loosened to an OR match, or a plain unfiltered query — rather than
|
|
73
|
+
only varying the search phrase.
|
|
74
|
+
|
|
75
|
+
6. If a specific recall miss turns out to have a narrow, demonstrable cause (a filter mismatch,
|
|
76
|
+
a stale tag, a wrong category), stop there. Report that cause and move on — do not generalize
|
|
77
|
+
one miss into a theory about the memory system being broken and then go build measurement or
|
|
78
|
+
debugging work around the theory. Confirm the narrow cause is insufficient before entertaining
|
|
79
|
+
a systemic explanation, not after.
|
|
80
|
+
|
|
64
81
|
## Follow the current user policy
|
|
65
82
|
|
|
66
83
|
The installer questionnaire configures a dynamic Empirical policy. It is user-scoped and may
|
|
@@ -113,6 +130,13 @@ Two rules that are not optional:
|
|
|
113
130
|
Both dedupe against near-duplicate existing memories and auto-link related ones by default; pass
|
|
114
131
|
`--no-dedupe` / `--no-auto-link` on the CLI only when you deliberately want to skip one of those.
|
|
115
132
|
|
|
133
|
+
**One idea per memory, not a bundle.** When a turn produces several distinct facts or ideas
|
|
134
|
+
worth keeping, record each as its own call rather than folding them into one large memory.
|
|
135
|
+
`record_graph_memory` (and `empirical memory record`) can and should be called multiple times
|
|
136
|
+
in the same turn — that is expected usage, not overhead to be minimized. A compound memory is
|
|
137
|
+
harder to find later (a query matching one idea in it won't surface the others as well) and
|
|
138
|
+
harder to link precisely into the graph than several atomic ones.
|
|
139
|
+
|
|
116
140
|
**Record facts about the USER, not about your own work.** You are a scribe here. "Matt prefers
|
|
117
141
|
short status updates" is a memory; "refactored the cluster selector, tests pass" is your diary
|
|
118
142
|
and belongs in the work-history skill, tagged and with `mass: 1`. The distinction is the
|
|
@@ -123,6 +147,18 @@ Two rules that are not optional:
|
|
|
123
147
|
codebase, stop: that is work-history or scar-tissue, and filing it here makes it indistinguishable
|
|
124
148
|
from something the user actually said or lived. Downstream features read these as the user's life.
|
|
125
149
|
|
|
150
|
+
**Say which one it is: pass `authorType`.** Same distinction, made explicit in the data instead
|
|
151
|
+
of left to be guessed from which transport you happened to use. `authorType: "user"` when the
|
|
152
|
+
memory is about the user — something they said, decided, prefer, or are working toward.
|
|
153
|
+
`authorType: "agent"` when it is your own work. Do not set `"agent"` merely because you are the
|
|
154
|
+
one calling: an agent writing down a real fact about the user is recording a user-authored fact,
|
|
155
|
+
and that is the whole point of this tool. On the CLI it is `--author-type user|agent`. Omit it
|
|
156
|
+
when you genuinely cannot tell, and it stays unresolved rather than being invented.
|
|
157
|
+
|
|
158
|
+
This is read back at retrieval time to keep an agent's session notes out of answers about the
|
|
159
|
+
user's own life and work, so a wrong value does not show up as a warning, it shows up as the
|
|
160
|
+
wrong memories coming back.
|
|
161
|
+
|
|
126
162
|
- Append a correction or follow-up to an existing memory with `add_note_by_query` / `empirical memory note`.
|
|
127
163
|
- Patch an existing memory with `update_memory_by_query` / `empirical memory update`.
|
|
128
164
|
- Delete only when the user explicitly asks; use the delete tool with confirmation, or `empirical memory delete --match "<memory>" --confirm`.
|
|
@@ -44,6 +44,8 @@ If you are not sure whether a task qualifies, treat it as if it does.
|
|
|
44
44
|
Add `--category` or `--tags` when known. Omit `--pretty` for routine recall because graph-rich
|
|
45
45
|
results can be large. Use `empirical memory list --recent` (see `empirical doc memory` for the
|
|
46
46
|
exact flag) for "recent/latest/today" requests instead of a match query.
|
|
47
|
+
When the user asks for every record of a topic, add `--path list`; when they ask what a
|
|
48
|
+
memory connects to, add `--path graph`.
|
|
47
49
|
3. Scope recall to the project you are in. Derive a project tag from the repository name
|
|
48
50
|
(`git rev-parse --show-toplevel`, then the folder name) or, outside a repo, the working
|
|
49
51
|
directory name, lowercased with spaces as hyphens. Add `--tags <project>` to every
|
|
@@ -52,6 +54,18 @@ If you are not sure whether a task qualifies, treat it as if it does.
|
|
|
52
54
|
facts about this one.
|
|
53
55
|
4. A slow response alone is API latency, not an outage; report an outage only when the CLI returns
|
|
54
56
|
an actual error, and use `empirical doctor` to verify connectivity.
|
|
57
|
+
5. A filtered query (`--category`, `--tags`, `--tags-all`, `--status`) that returns nothing is not
|
|
58
|
+
proof the memory doesn't exist — it only proves nothing matched that exact filter combination.
|
|
59
|
+
A memory's real category or tag convention often does not match how you or the user would
|
|
60
|
+
naturally describe it in conversation (e.g. described as "an idea" but stored under a different
|
|
61
|
+
category; tagged with a project's short name where you tried its full one). Before concluding a
|
|
62
|
+
memory doesn't exist, retry at least once with the filter relaxed or removed — category dropped,
|
|
63
|
+
tags loosened, or a plain unfiltered `--match` — rather than only varying the search phrase.
|
|
64
|
+
6. If a specific recall miss turns out to have a narrow, demonstrable cause (a filter mismatch, a
|
|
65
|
+
stale tag, a wrong category), stop there. Report that cause and move on — do not generalize one
|
|
66
|
+
miss into a theory about the memory system being broken and then go build measurement or
|
|
67
|
+
debugging work around the theory. Confirm the narrow cause is insufficient before entertaining a
|
|
68
|
+
systemic explanation, not after.
|
|
55
69
|
|
|
56
70
|
## Follow the current user policy
|
|
57
71
|
|
|
@@ -105,6 +119,13 @@ Two rules that are not optional:
|
|
|
105
119
|
first. Pass `--no-dedupe` or `--no-auto-link` only when you specifically want to bypass one of
|
|
106
120
|
those, which should be rare.
|
|
107
121
|
|
|
122
|
+
**One idea per memory, not a bundle.** When a turn produces several distinct facts or ideas
|
|
123
|
+
worth keeping, record each with its own `empirical memory record` call rather than folding them
|
|
124
|
+
into one large memory. Calling it multiple times in the same turn is expected usage, not
|
|
125
|
+
overhead to be minimized. A compound memory is harder to find later (a query matching one idea
|
|
126
|
+
in it won't surface the others as well) and harder to link precisely into the graph than several
|
|
127
|
+
atomic ones.
|
|
128
|
+
|
|
108
129
|
**Record facts about the USER, not about your own work.** You are a scribe here. "Matt prefers
|
|
109
130
|
short status updates" is a memory; "refactored the cluster selector, tests pass" is your diary
|
|
110
131
|
and belongs in the work-history skill, tagged and with `mass: 1`. The distinction is the
|
|
@@ -115,6 +136,18 @@ Two rules that are not optional:
|
|
|
115
136
|
codebase, stop: that is work-history or scar-tissue, and filing it here makes it indistinguishable
|
|
116
137
|
from something the user actually said or lived. Downstream features read these as the user's life.
|
|
117
138
|
|
|
139
|
+
**Say which one it is: pass `authorType`.** Same distinction, made explicit in the data instead
|
|
140
|
+
of left to be guessed from which transport you happened to use. `authorType: "user"` when the
|
|
141
|
+
memory is about the user — something they said, decided, prefer, or are working toward.
|
|
142
|
+
`authorType: "agent"` when it is your own work. Do not set `"agent"` merely because you are the
|
|
143
|
+
one calling: an agent writing down a real fact about the user is recording a user-authored fact,
|
|
144
|
+
and that is the whole point of this tool. On the CLI it is `--author-type user|agent`. Omit it
|
|
145
|
+
when you genuinely cannot tell, and it stays unresolved rather than being invented.
|
|
146
|
+
|
|
147
|
+
This is read back at retrieval time to keep an agent's session notes out of answers about the
|
|
148
|
+
user's own life and work, so a wrong value does not show up as a warning, it shows up as the
|
|
149
|
+
wrong memories coming back.
|
|
150
|
+
|
|
118
151
|
- Append a correction or follow-up to an existing memory with `empirical memory note`.
|
|
119
152
|
- Patch an existing memory with `empirical memory update`.
|
|
120
153
|
- Delete only when the user explicitly asks: `empirical memory delete --match "<memory>" --confirm`.
|
|
@@ -39,9 +39,24 @@ If you are not sure whether a task qualifies, treat it as if it does.
|
|
|
39
39
|
`nodeType`, and `monthKey` when they improve recall. For "recent/latest/today" requests, prefer
|
|
40
40
|
`list_memories` instead — it is the tool built for that shape of request. For exhaustive topic
|
|
41
41
|
retrieval, use the topic as a tag and follow pagination.
|
|
42
|
+
When the user asks for every record of a topic use `list_memories` with that topic as a tag,
|
|
43
|
+
and when they ask what a memory connects to use `get_memory_neighbors`.
|
|
42
44
|
5. A slow response alone is API latency, not an outage; report an outage only when a tool call
|
|
43
45
|
returns an actual error, and use `get_empirical_policy` or a retry to verify connectivity before
|
|
44
46
|
escalating.
|
|
47
|
+
6. A filtered `query_memories` call (`category`, `tags`, `nodeType`) that returns nothing is not
|
|
48
|
+
proof the memory doesn't exist — it only proves nothing matched that exact filter combination.
|
|
49
|
+
A memory's real category or tag convention often does not match how you or the user would
|
|
50
|
+
naturally describe it in conversation (e.g. described as "an idea" but stored under a
|
|
51
|
+
different category; tagged with a project's short name where you tried its full one). Before
|
|
52
|
+
concluding a memory doesn't exist, retry at least once with the filter relaxed or removed —
|
|
53
|
+
category dropped, tags loosened to an OR match, or a plain unfiltered query — rather than only
|
|
54
|
+
varying the search phrase.
|
|
55
|
+
7. If a specific recall miss turns out to have a narrow, demonstrable cause (a filter mismatch, a
|
|
56
|
+
stale tag, a wrong category), stop there. Report that cause and move on — do not generalize one
|
|
57
|
+
miss into a theory about the memory system being broken and then go build measurement or
|
|
58
|
+
debugging work around the theory. Confirm the narrow cause is insufficient before entertaining a
|
|
59
|
+
systemic explanation, not after.
|
|
45
60
|
|
|
46
61
|
## Follow the current user policy
|
|
47
62
|
|
|
@@ -82,6 +97,13 @@ Two rules that are not optional:
|
|
|
82
97
|
with the `record_graph_memory` MCP tool. It runs write-intent classification, dedupe, and
|
|
83
98
|
auto-linking automatically — you do not need to check for duplicates yourself first.
|
|
84
99
|
|
|
100
|
+
**One idea per memory, not a bundle.** When a turn produces several distinct facts or ideas
|
|
101
|
+
worth keeping, record each with its own `record_graph_memory` call rather than folding them
|
|
102
|
+
into one large memory. Calling it multiple times in the same turn is expected usage, not
|
|
103
|
+
overhead to be minimized. A compound memory is harder to find later (a query matching one idea
|
|
104
|
+
in it won't surface the others as well) and harder to link precisely into the graph than several
|
|
105
|
+
atomic ones.
|
|
106
|
+
|
|
85
107
|
**Record facts about the USER, not about your own work.** You are a scribe here. "Matt prefers
|
|
86
108
|
short status updates" is a memory; "refactored the cluster selector, tests pass" is your diary
|
|
87
109
|
and belongs in the work-history skill, tagged and with `mass: 1`. The distinction is the
|
|
@@ -92,6 +114,18 @@ Two rules that are not optional:
|
|
|
92
114
|
codebase, stop: that is work-history or scar-tissue, and filing it here makes it indistinguishable
|
|
93
115
|
from something the user actually said or lived. Downstream features read these as the user's life.
|
|
94
116
|
|
|
117
|
+
**Say which one it is: pass `authorType`.** Same distinction, made explicit in the data instead
|
|
118
|
+
of left to be guessed from which transport you happened to use. `authorType: "user"` when the
|
|
119
|
+
memory is about the user — something they said, decided, prefer, or are working toward.
|
|
120
|
+
`authorType: "agent"` when it is your own work. Do not set `"agent"` merely because you are the
|
|
121
|
+
one calling: an agent writing down a real fact about the user is recording a user-authored fact,
|
|
122
|
+
and that is the whole point of this tool. On the CLI it is `--author-type user|agent`. Omit it
|
|
123
|
+
when you genuinely cannot tell, and it stays unresolved rather than being invented.
|
|
124
|
+
|
|
125
|
+
This is read back at retrieval time to keep an agent's session notes out of answers about the
|
|
126
|
+
user's own life and work, so a wrong value does not show up as a warning, it shows up as the
|
|
127
|
+
wrong memories coming back.
|
|
128
|
+
|
|
95
129
|
- Append a correction or follow-up to an existing memory with `add_note_by_query`.
|
|
96
130
|
- Patch an existing memory with `update_memory_by_query`.
|
|
97
131
|
- Delete only when the user explicitly asks; use `delete_memory_by_query` with confirmation.
|
package/lib/memoryCommands.js
CHANGED
|
@@ -6,7 +6,7 @@ const { log, warn } = require('./ui');
|
|
|
6
6
|
const { httpJson, printApiError } = require('./http');
|
|
7
7
|
const { parseOption, parseFlag } = require('./env');
|
|
8
8
|
const { requireAccessToken, resolveApiBaseUrl, buildApiAuthHeaders } = require('./auth');
|
|
9
|
-
const { MEMORY_CATEGORIES, DOC_TOPICS } = require('./constants');
|
|
9
|
+
const { MEMORY_CATEGORIES, NODE_TYPES, DOC_TOPICS } = require('./constants');
|
|
10
10
|
const { withSelectedWorkspaceContext } = require('./workspaceContext');
|
|
11
11
|
|
|
12
12
|
// ─── Table rendering (--format table) ─────────────────────────────────────────
|
|
@@ -34,20 +34,32 @@ function renderMemoryResultsTable(items) {
|
|
|
34
34
|
id: m.id || m._id || '',
|
|
35
35
|
category: m.category || '',
|
|
36
36
|
summary: truncateForTable(m.summary, 60),
|
|
37
|
-
|
|
37
|
+
// The recall front door emits `score` (toQueryRow plus score in mcp/tools/memory.ts); the
|
|
38
|
+
// older /query route emitted rankScore/similarityScore. Read all three so the column is not
|
|
39
|
+
// blank against a real /recall row. On the list pathways score is deliberately absent, and
|
|
40
|
+
// an empty cell is the honest rendering of "this pathway does not rank".
|
|
41
|
+
score: m.score != null ? m.score : (m.rankScore != null ? m.rankScore : (m.similarityScore != null ? m.similarityScore : ''))
|
|
38
42
|
}));
|
|
39
43
|
return renderTable(rows, ['id', 'category', 'summary', 'score']);
|
|
40
44
|
}
|
|
41
45
|
|
|
46
|
+
// The retrieval pathways `memory query --path` may pin. Same four names the server's shared
|
|
47
|
+
// normalizer accepts (packages/server/src/services/recallParams.service.ts). Omitting --path
|
|
48
|
+
// leaves the classifier deciding, exactly as before.
|
|
49
|
+
const RECALL_PATHS = ['semantic', 'list', 'graph', 'hebbian'];
|
|
50
|
+
const RECALL_HOPS = [1, 2];
|
|
51
|
+
// Hops only mean something on a pathway that walks relationships.
|
|
52
|
+
const RECALL_HOP_PATHS = ['graph', 'hebbian'];
|
|
53
|
+
|
|
42
54
|
// ─── Scoped `--help` (C-005 item 5) ───────────────────────────────────────────
|
|
43
55
|
// `empirical memory --help` / `empirical memory <sub> --help` print scoped usage instead of
|
|
44
56
|
// the 90-line global help wall bin/empirical.js otherwise short-circuits every --help to.
|
|
45
57
|
|
|
46
58
|
const MEMORY_SUBCOMMAND_USAGE = {
|
|
47
|
-
query: 'Usage: empirical memory query --match <query> [--top-k 3] [--category <cat>] [--tags <t1,t2>] [--tags-all <t1,t2>] [--status <s1,s2>] [--pretty] [--format table]',
|
|
59
|
+
query: 'Usage: empirical memory query --match <query> [--top-k 3] [--category <cat>] [--tags <t1,t2>] [--tags-all <t1,t2>] [--status <s1,s2>] [--path semantic|list|graph|hebbian] [--hops 1|2] [--pretty] [--format table]',
|
|
48
60
|
get: 'Usage: empirical memory get --memory-id <id> [--pretty]',
|
|
49
61
|
list: 'Usage: empirical memory list [--category <cat>] [--tags <t1,t2>] [--start-date <ISO>] [--end-date <ISO>] [--limit 20] [--cursor <cursor>] [--oldest] [--pretty] [--format table]',
|
|
50
|
-
record: 'Usage: empirical memory record --category <cat> (--summary <text> | --summary - | --summary-file <path>) [--tags t1,t2] [--node-type observation] [--json-file <path>] [--no-dedupe] [--no-auto-link]',
|
|
62
|
+
record: 'Usage: empirical memory record --category <cat> (--summary <text> | --summary - | --summary-file <path>) [--tags t1,t2] [--node-type observation] [--json-file <path>] [--no-dedupe] [--no-auto-link] [--author-type user|agent]',
|
|
51
63
|
note: 'Usage: empirical memory note --text <note> (--match <query> | --memory-id <id>) [--min-score 0.35]',
|
|
52
64
|
update: 'Usage: empirical memory update (--match <query> | --memory-id <id>) (--patch-json <json> | --json-file <path>) [--min-score 0.35]',
|
|
53
65
|
delete: 'Usage: empirical memory delete (--match <query> | --memory-id <id>) [--confirm] [--min-score 0.35]\nUsage: empirical memory delete (--tag <t> | --category <c> | --all) --confirm',
|
|
@@ -179,7 +191,7 @@ function printMemoryResolveError(result) {
|
|
|
179
191
|
async function runMemoryQuery(args) {
|
|
180
192
|
const match = parseOption(args, '--match', '');
|
|
181
193
|
if (!match) {
|
|
182
|
-
warn(
|
|
194
|
+
warn(MEMORY_SUBCOMMAND_USAGE.query);
|
|
183
195
|
process.exit(1);
|
|
184
196
|
}
|
|
185
197
|
|
|
@@ -197,29 +209,79 @@ async function runMemoryQuery(args) {
|
|
|
197
209
|
const pretty = parseFlag(args, '--pretty');
|
|
198
210
|
const format = parseOption(args, '--format', 'json');
|
|
199
211
|
|
|
212
|
+
// --path / --hops are validated HERE, where the value is decided, so a typo costs no round
|
|
213
|
+
// trip and the message names the four pathways. The server validates the same way (the shared
|
|
214
|
+
// normalizer in packages/server/src/services/recallParams.service.ts); this is a fast local
|
|
215
|
+
// echo of that contract, never a second, looser one.
|
|
216
|
+
const pathRaw = parseOption(args, '--path', '');
|
|
217
|
+
let path = '';
|
|
218
|
+
if (pathRaw) {
|
|
219
|
+
path = String(pathRaw).trim().toLowerCase();
|
|
220
|
+
if (!RECALL_PATHS.includes(path)) {
|
|
221
|
+
warn(`--path must be one of: ${RECALL_PATHS.join(', ')} (received ${JSON.stringify(pathRaw)})`);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const hopsRaw = parseOption(args, '--hops', '');
|
|
227
|
+
let hops = null;
|
|
228
|
+
if (hopsRaw) {
|
|
229
|
+
const parsedHops = Number(hopsRaw);
|
|
230
|
+
if (!RECALL_HOPS.includes(parsedHops)) {
|
|
231
|
+
warn(`--hops must be 1 or 2 (received ${JSON.stringify(hopsRaw)})`);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
// Silently ignoring --hops on a ranked or list pathway would look like it took effect.
|
|
235
|
+
if (!RECALL_HOP_PATHS.includes(path)) {
|
|
236
|
+
warn(`--hops only applies with --path graph or --path hebbian${path ? ` (received --path ${path})` : ' (no --path given)'}`);
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
hops = parsedHops;
|
|
240
|
+
}
|
|
241
|
+
|
|
200
242
|
const accessToken = requireAccessToken();
|
|
201
243
|
const baseUrl = resolveApiBaseUrl();
|
|
202
|
-
|
|
244
|
+
// The recall front door: the same answer the MCP query_memories tool gives, which /query never
|
|
245
|
+
// gave (classifier routing, the list block, the exact-tag retry, agent-note exclusion,
|
|
246
|
+
// retrievalPath). It takes its parameters FLAT, matching the shared normalizer, not nested
|
|
247
|
+
// under `filters` the way the older /query route did. Send one shape only.
|
|
248
|
+
const queryUrl = new URL('/api/v1/memories/recall', baseUrl).toString();
|
|
203
249
|
|
|
204
250
|
const body = { query: match, topK };
|
|
205
|
-
|
|
206
|
-
if (
|
|
207
|
-
if (
|
|
208
|
-
if (
|
|
209
|
-
if (
|
|
210
|
-
if (
|
|
251
|
+
if (category) body.category = category;
|
|
252
|
+
if (tags.length) body.tags = tags;
|
|
253
|
+
if (tagsAll.length) body.tagsAll = tagsAll;
|
|
254
|
+
if (status.length) body.status = status;
|
|
255
|
+
if (path) body.path = path;
|
|
256
|
+
if (hops !== null) body.hops = hops;
|
|
211
257
|
|
|
212
258
|
let response;
|
|
213
259
|
try {
|
|
214
260
|
response = await httpJson(queryUrl, 'POST', body, buildApiAuthHeaders(accessToken));
|
|
215
261
|
} catch (error) {
|
|
262
|
+
// A 404 here means the server predates the recall front door. Say that plainly instead of
|
|
263
|
+
// rerouting to /query: that fallback would quietly answer through a different routing path
|
|
264
|
+
// with different defaults (topK 10 against 3) and keep the old route alive forever.
|
|
265
|
+
if (error && error.statusCode === 404) {
|
|
266
|
+
warn(JSON.stringify({
|
|
267
|
+
error: true,
|
|
268
|
+
code: 'recall_route_missing',
|
|
269
|
+
message: 'This server does not serve /api/v1/memories/recall yet. Upgrade the Empirical server, or use an older CLI against it.'
|
|
270
|
+
}));
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
216
273
|
printApiError(error);
|
|
217
274
|
process.exit(1);
|
|
218
275
|
}
|
|
219
276
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
277
|
+
// The front door answers with data.memories; the older /query route answered with data.results.
|
|
278
|
+
// Both are accepted so a CLI pointed at a server that predates /recall still prints rows.
|
|
279
|
+
const data = response && response.data ? response.data : null;
|
|
280
|
+
const results = (data && Array.isArray(data.memories))
|
|
281
|
+
? data.memories
|
|
282
|
+
: ((data && Array.isArray(data.results))
|
|
283
|
+
? data.results
|
|
284
|
+
: (Array.isArray(response) ? response : []));
|
|
223
285
|
|
|
224
286
|
if (format === 'table') {
|
|
225
287
|
log(renderMemoryResultsTable(results));
|
|
@@ -351,8 +413,9 @@ async function runMemoryRecord(args) {
|
|
|
351
413
|
}
|
|
352
414
|
|
|
353
415
|
if (!category || !summary) {
|
|
354
|
-
warn('Usage: empirical memory record --category <cat> (--summary <text> | --summary - | --summary-file <path>) [--tags t1,t2] [--node-type observation] [--json-file <path>] [--no-dedupe] [--no-auto-link]');
|
|
416
|
+
warn('Usage: empirical memory record --category <cat> (--summary <text> | --summary - | --summary-file <path>) [--tags t1,t2] [--node-type observation] [--json-file <path>] [--no-dedupe] [--no-auto-link] [--author-type user|agent]');
|
|
355
417
|
warn('Valid categories: ' + MEMORY_CATEGORIES.join(', '));
|
|
418
|
+
warn('Valid node types: ' + NODE_TYPES.join(', '));
|
|
356
419
|
process.exit(1);
|
|
357
420
|
}
|
|
358
421
|
|
|
@@ -369,6 +432,15 @@ async function runMemoryRecord(args) {
|
|
|
369
432
|
const tags = tagsRaw ? tagsRaw.split(',').map((t) => t.trim()).filter(Boolean) : [];
|
|
370
433
|
const nodeType = parseOption(args, '--node-type', 'observation');
|
|
371
434
|
|
|
435
|
+
if (!NODE_TYPES.includes(nodeType)) {
|
|
436
|
+
warn(JSON.stringify({
|
|
437
|
+
error: true,
|
|
438
|
+
code: 'invalid_node_type',
|
|
439
|
+
message: `Invalid nodeType "${nodeType}". Valid: ${NODE_TYPES.join(', ')}`
|
|
440
|
+
}));
|
|
441
|
+
process.exit(1);
|
|
442
|
+
}
|
|
443
|
+
|
|
372
444
|
payload = { category, summary, nodeType, data: { text: String(summary) } };
|
|
373
445
|
if (tags.length) payload.tags = tags;
|
|
374
446
|
}
|
|
@@ -395,6 +467,29 @@ async function runMemoryRecord(args) {
|
|
|
395
467
|
payload.autoLink = false;
|
|
396
468
|
}
|
|
397
469
|
|
|
470
|
+
// Authorship (2026-09-09). `source` is observable truth: this is the CLI, so say so.
|
|
471
|
+
// Until now the CLI sent nothing, the server fell back to the transport it saw ('api'),
|
|
472
|
+
// and api maps to authorType 'user' because that mapping was written for the web portal.
|
|
473
|
+
// Every agent write through this command was therefore stored as authored by the user.
|
|
474
|
+
// 'cli' resolves to authorType 'unknown', which is the honest answer when nobody says.
|
|
475
|
+
if (payload.source === undefined) payload.source = 'cli';
|
|
476
|
+
|
|
477
|
+
// --author-type lets the caller state what the transport cannot know. An agent recording
|
|
478
|
+
// its own session notes should pass 'agent'; an agent recording something the user said
|
|
479
|
+
// or decided should pass 'user'. Omitted, it stays 'unknown' rather than being guessed.
|
|
480
|
+
const authorType = parseOption(args, '--author-type', '');
|
|
481
|
+
if (authorType) {
|
|
482
|
+
if (!['user', 'agent', 'system'].includes(authorType)) {
|
|
483
|
+
warn(JSON.stringify({
|
|
484
|
+
error: true,
|
|
485
|
+
code: 'invalid_author_type',
|
|
486
|
+
message: `Invalid --author-type "${authorType}". Valid: user, agent, system`
|
|
487
|
+
}));
|
|
488
|
+
process.exit(1);
|
|
489
|
+
}
|
|
490
|
+
payload.authorType = authorType;
|
|
491
|
+
}
|
|
492
|
+
|
|
398
493
|
const accessToken = requireAccessToken();
|
|
399
494
|
const baseUrl = resolveApiBaseUrl();
|
|
400
495
|
const recordUrl = new URL('/api/v1/memories', baseUrl).toString();
|