agenr 0.7.13 → 0.7.14
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/CHANGELOG.md +12 -0
- package/README.md +4 -5
- package/dist/cli-main.d.ts +1 -0
- package/dist/cli-main.js +257 -121
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.7.14] - 2026-02-21
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- feat(recall): added `until` upper date bound to recall query filtering in CLI, MCP, and DB recall paths (`since` + `until` now define an inclusive window)
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
- fix(recall): recency decay now anchors to the `until` ceiling for historical windows while freshness boost remains anchored to real query time
|
|
10
|
+
- fix(recall): centralized `parseSinceToIso` in `src/utils/time.ts` and removed duplicate implementations from recall CLI and MCP server
|
|
11
|
+
- fix(recall): added inverted date-range validation - recall now returns a descriptive error when `since > until` instead of returning an empty list
|
|
12
|
+
- fix(recall): interim 3x candidate over-fetch under date bounds to improve in-window recall coverage until SQL-level date filtering is added
|
|
13
|
+
- fix(recall): corrupt `created_at` values are now safely excluded under date-bound filters instead of leaking invalid rows into filtered recall
|
|
14
|
+
|
|
3
15
|
## [0.7.13] - 2026-02-21
|
|
4
16
|
|
|
5
17
|
### Fixed
|
package/README.md
CHANGED
|
@@ -161,7 +161,7 @@ agenr watch --platform openclaw --context ~/.agenr/CONTEXT.md
|
|
|
161
161
|
|
|
162
162
|
## How it works
|
|
163
163
|
|
|
164
|
-
**Extract** - An LLM reads your transcripts and pulls out structured entries: facts, decisions, preferences, todos, relationships, events, lessons. Smart filtering removes noise (tool calls, file contents, boilerplate) before the LLM ever sees it.
|
|
164
|
+
**Extract** - An LLM reads your transcripts and pulls out structured entries: facts, decisions, preferences, todos, relationships, events, lessons. Smart filtering removes noise (tool calls, file contents, boilerplate) before the LLM ever sees it. For OpenClaw sessions, hedged or unverified agent claims are detected and capped at importance 5 with an `unverified` tag - so speculative assistant statements do not pollute your memory as facts.
|
|
165
165
|
|
|
166
166
|
**Store** - Entries get embedded and compared against what's already in the database. Near-duplicates reinforce existing knowledge. New information gets inserted. Online dedup catches copies in real-time.
|
|
167
167
|
|
|
@@ -201,7 +201,7 @@ MCP settings.
|
|
|
201
201
|
| `agenr ingest <paths...>` | Bulk-ingest files and directories |
|
|
202
202
|
| `agenr extract <files...>` | Extract knowledge entries from text files |
|
|
203
203
|
| `agenr store [files...]` | Store entries with semantic dedup |
|
|
204
|
-
| `agenr recall [query]` | Semantic + memory-aware recall |
|
|
204
|
+
| `agenr recall [query]` | Semantic + memory-aware recall. Use `--since` / `--until` for date range queries (e.g. `--since 14d --until 7d` for entries from two weeks ago). |
|
|
205
205
|
| `agenr retire [subject]` | Retire a stale entry (hidden, not deleted). Match by subject text or use --id <id> to target by entry ID. |
|
|
206
206
|
| `agenr watch [file]` | Live-watch files/directories, auto-extract knowledge |
|
|
207
207
|
| `agenr daemon install` | Install background watch daemon (macOS launchd) |
|
|
@@ -227,13 +227,12 @@ Deep dive: [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md)
|
|
|
227
227
|
|
|
228
228
|
## Status
|
|
229
229
|
|
|
230
|
-
The core pipeline is stable and tested (
|
|
230
|
+
The core pipeline is stable and tested (841 tests). We use it daily managing
|
|
231
231
|
thousands of knowledge entries across OpenClaw sessions.
|
|
232
232
|
|
|
233
233
|
What works: extraction, storage, recall, MCP integration, online dedup, consolidation, smart filtering, live watching, daemon mode.
|
|
234
234
|
|
|
235
|
-
What's next: Cursor live signals, Claude Code UserPromptSubmit adapter,
|
|
236
|
-
transitive project dependencies.
|
|
235
|
+
What's next: GUI Management Console (browse, search, and curate your knowledge database visually), Cursor live signals, Claude Code UserPromptSubmit adapter, transitive project dependencies.
|
|
237
236
|
|
|
238
237
|
## Philosophy
|
|
239
238
|
|
package/dist/cli-main.d.ts
CHANGED
|
@@ -171,6 +171,7 @@ declare function extractKnowledgeFromChunks(params: {
|
|
|
171
171
|
chunks: TranscriptChunk[];
|
|
172
172
|
client: LlmClient;
|
|
173
173
|
verbose: boolean;
|
|
174
|
+
platform?: KnowledgePlatform | (string & {});
|
|
174
175
|
noDedup?: boolean;
|
|
175
176
|
interChunkDelayMs?: number;
|
|
176
177
|
llmConcurrency?: number;
|
package/dist/cli-main.js
CHANGED
|
@@ -3335,6 +3335,47 @@ function resolveEmbeddingApiKey(config, env = process.env) {
|
|
|
3335
3335
|
);
|
|
3336
3336
|
}
|
|
3337
3337
|
|
|
3338
|
+
// src/utils/time.ts
|
|
3339
|
+
var DURATION_PATTERN = /^(\d+)\s*([hdmy])$/i;
|
|
3340
|
+
var UNIT_TO_MILLISECONDS = {
|
|
3341
|
+
h: 1e3 * 60 * 60,
|
|
3342
|
+
d: 1e3 * 60 * 60 * 24,
|
|
3343
|
+
m: 1e3 * 60 * 60 * 24 * 30,
|
|
3344
|
+
y: 1e3 * 60 * 60 * 24 * 365
|
|
3345
|
+
};
|
|
3346
|
+
function parseSince(since, now = /* @__PURE__ */ new Date()) {
|
|
3347
|
+
if (!since) {
|
|
3348
|
+
return void 0;
|
|
3349
|
+
}
|
|
3350
|
+
const trimmed = since.trim();
|
|
3351
|
+
if (!trimmed) {
|
|
3352
|
+
return void 0;
|
|
3353
|
+
}
|
|
3354
|
+
const durationMatch = trimmed.match(DURATION_PATTERN);
|
|
3355
|
+
if (durationMatch) {
|
|
3356
|
+
const amount = Number(durationMatch[1]);
|
|
3357
|
+
const unit = durationMatch[2]?.toLowerCase();
|
|
3358
|
+
const multiplier = unit ? UNIT_TO_MILLISECONDS[unit] : void 0;
|
|
3359
|
+
if (!Number.isFinite(amount) || amount <= 0 || !multiplier) {
|
|
3360
|
+
throw new Error("Invalid since value");
|
|
3361
|
+
}
|
|
3362
|
+
return new Date(now.getTime() - amount * multiplier);
|
|
3363
|
+
}
|
|
3364
|
+
const parsed = new Date(trimmed);
|
|
3365
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
3366
|
+
throw new Error("Invalid since value");
|
|
3367
|
+
}
|
|
3368
|
+
return parsed;
|
|
3369
|
+
}
|
|
3370
|
+
function parseSinceToIso(since, now = /* @__PURE__ */ new Date(), invalidMessage = "Invalid date value") {
|
|
3371
|
+
try {
|
|
3372
|
+
const parsed = parseSince(since, now);
|
|
3373
|
+
return parsed ? parsed.toISOString() : void 0;
|
|
3374
|
+
} catch {
|
|
3375
|
+
throw new Error(invalidMessage);
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3338
3379
|
// src/db/session-start.ts
|
|
3339
3380
|
var DEFAULT_SESSION_CANDIDATE_LIMIT = 500;
|
|
3340
3381
|
var DEFAULT_CORE_CANDIDATE_LIMIT = 5e3;
|
|
@@ -3641,40 +3682,6 @@ function resolveScopeSet(scope) {
|
|
|
3641
3682
|
}
|
|
3642
3683
|
return /* @__PURE__ */ new Set(["private", "personal", "public"]);
|
|
3643
3684
|
}
|
|
3644
|
-
function parseSince(since, now) {
|
|
3645
|
-
if (!since) {
|
|
3646
|
-
return void 0;
|
|
3647
|
-
}
|
|
3648
|
-
const trimmed = since.trim();
|
|
3649
|
-
if (!trimmed) {
|
|
3650
|
-
return void 0;
|
|
3651
|
-
}
|
|
3652
|
-
const durationMatch = trimmed.match(/^(\d+)\s*([hdy])$/i);
|
|
3653
|
-
if (durationMatch) {
|
|
3654
|
-
const amount = Number(durationMatch[1]);
|
|
3655
|
-
const unit = durationMatch[2]?.toLowerCase();
|
|
3656
|
-
if (!Number.isFinite(amount) || amount <= 0 || !unit) {
|
|
3657
|
-
return void 0;
|
|
3658
|
-
}
|
|
3659
|
-
let multiplier = 0;
|
|
3660
|
-
if (unit === "h") {
|
|
3661
|
-
multiplier = 1e3 * 60 * 60;
|
|
3662
|
-
} else if (unit === "d") {
|
|
3663
|
-
multiplier = MILLISECONDS_PER_DAY;
|
|
3664
|
-
} else if (unit === "y") {
|
|
3665
|
-
multiplier = MILLISECONDS_PER_DAY * 365;
|
|
3666
|
-
}
|
|
3667
|
-
if (multiplier <= 0) {
|
|
3668
|
-
return void 0;
|
|
3669
|
-
}
|
|
3670
|
-
return new Date(now.getTime() - amount * multiplier);
|
|
3671
|
-
}
|
|
3672
|
-
const parsed = new Date(trimmed);
|
|
3673
|
-
if (Number.isNaN(parsed.getTime())) {
|
|
3674
|
-
return void 0;
|
|
3675
|
-
}
|
|
3676
|
-
return parsed;
|
|
3677
|
-
}
|
|
3678
3685
|
function entryCreatedAfter(entry, cutoff) {
|
|
3679
3686
|
if (!cutoff) {
|
|
3680
3687
|
return true;
|
|
@@ -3685,10 +3692,23 @@ function entryCreatedAfter(entry, cutoff) {
|
|
|
3685
3692
|
}
|
|
3686
3693
|
return created.getTime() >= cutoff.getTime();
|
|
3687
3694
|
}
|
|
3688
|
-
function
|
|
3695
|
+
function entryCreatedBefore(entry, ceiling) {
|
|
3696
|
+
if (!ceiling) {
|
|
3697
|
+
return true;
|
|
3698
|
+
}
|
|
3699
|
+
const created = new Date(entry.created_at);
|
|
3700
|
+
if (Number.isNaN(created.getTime())) {
|
|
3701
|
+
return false;
|
|
3702
|
+
}
|
|
3703
|
+
return created.getTime() <= ceiling.getTime();
|
|
3704
|
+
}
|
|
3705
|
+
function passesFilters(entry, query, cutoff, ceiling, allowedScopes, normalizedTags, isSessionStart) {
|
|
3689
3706
|
if (entry.superseded_by) {
|
|
3690
3707
|
return false;
|
|
3691
3708
|
}
|
|
3709
|
+
if (entry.retired) {
|
|
3710
|
+
return false;
|
|
3711
|
+
}
|
|
3692
3712
|
if (isSessionStart && entry.suppressed_contexts?.includes("session-start")) {
|
|
3693
3713
|
return false;
|
|
3694
3714
|
}
|
|
@@ -3710,6 +3730,9 @@ function passesFilters(entry, query, cutoff, allowedScopes, normalizedTags, isSe
|
|
|
3710
3730
|
if (!entryCreatedAfter(entry, cutoff)) {
|
|
3711
3731
|
return false;
|
|
3712
3732
|
}
|
|
3733
|
+
if (!entryCreatedBefore(entry, ceiling)) {
|
|
3734
|
+
return false;
|
|
3735
|
+
}
|
|
3713
3736
|
const entryScope = entry.scope ?? "private";
|
|
3714
3737
|
if (!allowedScopes.has(entryScope)) {
|
|
3715
3738
|
return false;
|
|
@@ -3792,7 +3815,7 @@ function computeSpacingFactor(intervals, recallCount, createdAt, lastRecalledAt)
|
|
|
3792
3815
|
}
|
|
3793
3816
|
return Math.max(1, Math.log1p(maxGapDays + 1));
|
|
3794
3817
|
}
|
|
3795
|
-
function scoreEntryWithBreakdown(entry, vectorSim, ftsMatch, now) {
|
|
3818
|
+
function scoreEntryWithBreakdown(entry, vectorSim, ftsMatch, now, freshnessNow = now) {
|
|
3796
3819
|
const daysOld = parseDaysBetween(now, entry.created_at);
|
|
3797
3820
|
const daysSinceRecall = entry.last_recalled_at ? parseDaysBetween(now, entry.last_recalled_at) : daysOld;
|
|
3798
3821
|
const rawVector = clamp01(vectorSim);
|
|
@@ -3807,7 +3830,7 @@ function scoreEntryWithBreakdown(entry, vectorSim, ftsMatch, now) {
|
|
|
3807
3830
|
entry.last_recalled_at
|
|
3808
3831
|
);
|
|
3809
3832
|
const fts = ftsMatch ? 0.15 : 0;
|
|
3810
|
-
const fresh = freshnessBoost(entry,
|
|
3833
|
+
const fresh = freshnessBoost(entry, freshnessNow);
|
|
3811
3834
|
const spacedRecallBase = Math.min(recallBase * spacingFactor, 1);
|
|
3812
3835
|
const memoryStrength = Math.min(Math.max(imp, spacedRecallBase) * fresh, 1);
|
|
3813
3836
|
const todoPenalty = entry.type === "todo" ? todoStaleness(entry, now) : 1;
|
|
@@ -3893,6 +3916,7 @@ async function fetchVectorCandidates(db, queryEmbedding, limit, platform, projec
|
|
|
3893
3916
|
CROSS JOIN entries AS e ON e.rowid = v.id
|
|
3894
3917
|
WHERE e.embedding IS NOT NULL
|
|
3895
3918
|
AND e.superseded_by IS NULL
|
|
3919
|
+
AND e.retired = 0
|
|
3896
3920
|
${projectSql.clause}
|
|
3897
3921
|
${platform ? "AND e.platform = ?" : ""}
|
|
3898
3922
|
`,
|
|
@@ -3957,6 +3981,7 @@ async function fetchSessionCandidates(db, limit, context, platform, project, exc
|
|
|
3957
3981
|
suppressed_contexts
|
|
3958
3982
|
FROM entries
|
|
3959
3983
|
WHERE superseded_by IS NULL
|
|
3984
|
+
AND retired = 0
|
|
3960
3985
|
${context === "session-start" ? `AND (suppressed_contexts IS NULL OR suppressed_contexts NOT LIKE '%"session-start"%')` : ""}
|
|
3961
3986
|
${projectSql.clause}
|
|
3962
3987
|
${platform ? "AND platform = ?" : ""}
|
|
@@ -3999,6 +4024,7 @@ async function runFts(db, text2, platform, project, excludeProject, projectStric
|
|
|
3999
4024
|
JOIN entries AS e ON e.rowid = entries_fts.rowid
|
|
4000
4025
|
WHERE entries_fts MATCH ?
|
|
4001
4026
|
AND e.superseded_by IS NULL
|
|
4027
|
+
AND e.retired = 0
|
|
4002
4028
|
${projectSql.clause}
|
|
4003
4029
|
${platform ? "AND e.platform = ?" : ""}
|
|
4004
4030
|
LIMIT 250
|
|
@@ -4027,8 +4053,8 @@ async function updateRecallMetadata(db, ids, now) {
|
|
|
4027
4053
|
args: [now.toISOString(), epochSecs, ...ids]
|
|
4028
4054
|
});
|
|
4029
4055
|
}
|
|
4030
|
-
function scoreSessionEntry(entry,
|
|
4031
|
-
return scoreEntryWithBreakdown(entry, 1, false,
|
|
4056
|
+
function scoreSessionEntry(entry, effectiveNow, freshnessNow) {
|
|
4057
|
+
return scoreEntryWithBreakdown(entry, 1, false, effectiveNow, freshnessNow);
|
|
4032
4058
|
}
|
|
4033
4059
|
async function recall(db, query, apiKey, options = {}) {
|
|
4034
4060
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
@@ -4046,8 +4072,28 @@ async function recall(db, query, apiKey, options = {}) {
|
|
|
4046
4072
|
throw new Error("--no-boost requires query text.");
|
|
4047
4073
|
}
|
|
4048
4074
|
const normalizedTags = normalizeTags(query.tags);
|
|
4049
|
-
|
|
4075
|
+
let cutoff;
|
|
4076
|
+
try {
|
|
4077
|
+
cutoff = parseSince(query.since, now);
|
|
4078
|
+
} catch (error) {
|
|
4079
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
4080
|
+
const sinceValue = query.since ?? "";
|
|
4081
|
+
throw new Error(`Invalid since value "${sinceValue}": ${reason}`);
|
|
4082
|
+
}
|
|
4083
|
+
let ceiling;
|
|
4084
|
+
try {
|
|
4085
|
+
ceiling = parseSince(query.until, now);
|
|
4086
|
+
} catch (error) {
|
|
4087
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
4088
|
+
throw new Error(`Invalid until value "${query.until ?? ""}": ${reason}`);
|
|
4089
|
+
}
|
|
4090
|
+
if (cutoff !== void 0 && ceiling !== void 0 && cutoff > ceiling) {
|
|
4091
|
+
throw new Error(
|
|
4092
|
+
`Invalid date range: since (${cutoff.toISOString()}) must be earlier than until (${ceiling.toISOString()}). since sets the lower bound, until the upper bound.`
|
|
4093
|
+
);
|
|
4094
|
+
}
|
|
4050
4095
|
const allowedScopes = resolveScopeSet(query.scope);
|
|
4096
|
+
const hasDateBounds = cutoff !== void 0 || ceiling !== void 0;
|
|
4051
4097
|
let candidates;
|
|
4052
4098
|
let effectiveText = text2;
|
|
4053
4099
|
if (text2) {
|
|
@@ -4058,19 +4104,21 @@ async function recall(db, query, apiKey, options = {}) {
|
|
|
4058
4104
|
if (!queryEmbedding) {
|
|
4059
4105
|
throw new Error("Embedding provider returned no vector for recall query.");
|
|
4060
4106
|
}
|
|
4107
|
+
const vectorLimit = hasDateBounds ? (options.vectorCandidateLimit ?? DEFAULT_VECTOR_CANDIDATE_LIMIT) * 3 : options.vectorCandidateLimit ?? DEFAULT_VECTOR_CANDIDATE_LIMIT;
|
|
4061
4108
|
candidates = await fetchVectorCandidates(
|
|
4062
4109
|
db,
|
|
4063
4110
|
queryEmbedding,
|
|
4064
|
-
|
|
4111
|
+
vectorLimit,
|
|
4065
4112
|
platform,
|
|
4066
4113
|
project,
|
|
4067
4114
|
excludeProject,
|
|
4068
4115
|
projectStrict
|
|
4069
4116
|
);
|
|
4070
4117
|
} else {
|
|
4118
|
+
const sessionLimit = hasDateBounds ? (options.sessionCandidateLimit ?? DEFAULT_SESSION_CANDIDATE_LIMIT) * 3 : options.sessionCandidateLimit ?? DEFAULT_SESSION_CANDIDATE_LIMIT;
|
|
4071
4119
|
candidates = await fetchSessionCandidates(
|
|
4072
4120
|
db,
|
|
4073
|
-
|
|
4121
|
+
sessionLimit,
|
|
4074
4122
|
context,
|
|
4075
4123
|
platform,
|
|
4076
4124
|
project,
|
|
@@ -4079,16 +4127,17 @@ async function recall(db, query, apiKey, options = {}) {
|
|
|
4079
4127
|
);
|
|
4080
4128
|
}
|
|
4081
4129
|
const filtered = candidates.filter(
|
|
4082
|
-
(candidate) => passesFilters(candidate.entry, query, cutoff, allowedScopes, normalizedTags, isSessionStart)
|
|
4130
|
+
(candidate) => passesFilters(candidate.entry, query, cutoff, ceiling, allowedScopes, normalizedTags, isSessionStart)
|
|
4083
4131
|
);
|
|
4084
4132
|
if (filtered.length === 0) {
|
|
4085
4133
|
return [];
|
|
4086
4134
|
}
|
|
4087
4135
|
const ftsMatches = text2 && !query.noBoost ? await runFts(db, effectiveText, platform, project, excludeProject, projectStrict) : /* @__PURE__ */ new Set();
|
|
4136
|
+
const effectiveNow = ceiling ?? now;
|
|
4088
4137
|
const scored = filtered.map((candidate) => {
|
|
4089
4138
|
const ftsMatch = ftsMatches.has(candidate.entry.id);
|
|
4090
4139
|
if (!text2) {
|
|
4091
|
-
const sessionScore = scoreSessionEntry(candidate.entry, now);
|
|
4140
|
+
const sessionScore = scoreSessionEntry(candidate.entry, effectiveNow, now);
|
|
4092
4141
|
return {
|
|
4093
4142
|
entry: candidate.entry,
|
|
4094
4143
|
score: sessionScore.score,
|
|
@@ -4112,7 +4161,7 @@ async function recall(db, query, apiKey, options = {}) {
|
|
|
4112
4161
|
}
|
|
4113
4162
|
};
|
|
4114
4163
|
}
|
|
4115
|
-
const detailed = scoreEntryWithBreakdown(candidate.entry, candidate.vectorSim, ftsMatch, now);
|
|
4164
|
+
const detailed = scoreEntryWithBreakdown(candidate.entry, candidate.vectorSim, ftsMatch, effectiveNow, now);
|
|
4116
4165
|
return {
|
|
4117
4166
|
entry: candidate.entry,
|
|
4118
4167
|
score: detailed.score,
|
|
@@ -5849,6 +5898,7 @@ async function countActiveEntries(db, platform, project, excludeProject) {
|
|
|
5849
5898
|
SELECT COUNT(*) AS count
|
|
5850
5899
|
FROM entries
|
|
5851
5900
|
WHERE superseded_by IS NULL
|
|
5901
|
+
AND retired = 0
|
|
5852
5902
|
${platform ? "AND platform = ?" : ""}
|
|
5853
5903
|
${projectSql.clause}
|
|
5854
5904
|
`,
|
|
@@ -5890,6 +5940,7 @@ async function expireDecayedEntries(db, now, options) {
|
|
|
5890
5940
|
SELECT id, content, expiry, created_at
|
|
5891
5941
|
FROM entries
|
|
5892
5942
|
WHERE superseded_by IS NULL
|
|
5943
|
+
AND retired = 0
|
|
5893
5944
|
AND expiry = 'temporary'
|
|
5894
5945
|
${options.platform ? "AND platform = ?" : ""}
|
|
5895
5946
|
${projectSql.clause}
|
|
@@ -5952,6 +6003,7 @@ async function mergeNearExactDuplicates(db, options) {
|
|
|
5952
6003
|
SELECT COUNT(*) AS count
|
|
5953
6004
|
FROM entries
|
|
5954
6005
|
WHERE superseded_by IS NULL
|
|
6006
|
+
AND retired = 0
|
|
5955
6007
|
AND embedding IS NOT NULL
|
|
5956
6008
|
${options.platform ? "AND platform = ?" : ""}
|
|
5957
6009
|
${projectSql.clause}
|
|
@@ -5976,6 +6028,7 @@ async function mergeNearExactDuplicates(db, options) {
|
|
|
5976
6028
|
SELECT id, type, subject, content, project, embedding, confirmations, recall_count, created_at
|
|
5977
6029
|
FROM entries
|
|
5978
6030
|
WHERE superseded_by IS NULL
|
|
6031
|
+
AND retired = 0
|
|
5979
6032
|
AND embedding IS NOT NULL
|
|
5980
6033
|
${options.platform ? "AND platform = ?" : ""}
|
|
5981
6034
|
${projectSql.clause}
|
|
@@ -6362,6 +6415,7 @@ async function buildClusters(db, options = {}) {
|
|
|
6362
6415
|
recall_count, created_at, merged_from, consolidated_at
|
|
6363
6416
|
FROM entries
|
|
6364
6417
|
WHERE superseded_by IS NULL
|
|
6418
|
+
AND retired = 0
|
|
6365
6419
|
AND embedding IS NOT NULL
|
|
6366
6420
|
${platform ? "AND platform = ?" : ""}
|
|
6367
6421
|
${projectCondition}
|
|
@@ -9219,6 +9273,29 @@ PREFERENCE:
|
|
|
9219
9273
|
"source_context": "User mentioned scheduling preference during calendar discussion -- scored 6 not 8 because no parallel session needs to act on this immediately; it is a low-urgency convenience preference"
|
|
9220
9274
|
}
|
|
9221
9275
|
|
|
9276
|
+
// NOTE: These examples are drawn from OpenClaw transcripts (agent role labels, tool-verified claims). They provide soft cross-platform guidance for hedged-claim handling; mechanical enforcement (importance cap + unverified tag) is only applied for the openclaw platform via applyConfidenceCap().
|
|
9277
|
+
Example: hedged agent claim (correct handling):
|
|
9278
|
+
{
|
|
9279
|
+
"type": "fact",
|
|
9280
|
+
"subject": "agenr openclaw ingestion support",
|
|
9281
|
+
"content": "Agent expressed uncertainty about whether agenr supports OpenClaw session ingestion. This has not been tool-verified.",
|
|
9282
|
+
"importance": 5,
|
|
9283
|
+
"expiry": "temporary",
|
|
9284
|
+
"tags": ["agenr", "openclaw", "unverified"],
|
|
9285
|
+
"canonical_key": "agenr-openclaw-ingestion-support-uncertain"
|
|
9286
|
+
}
|
|
9287
|
+
|
|
9288
|
+
Example: tool-verified agent claim (correct handling):
|
|
9289
|
+
{
|
|
9290
|
+
"type": "fact",
|
|
9291
|
+
"subject": "agenr openclaw ingestion",
|
|
9292
|
+
"content": "agenr actively ingests OpenClaw sessions. Confirmed via exec output showing watch active on 3 files.",
|
|
9293
|
+
"importance": 7,
|
|
9294
|
+
"expiry": "temporary",
|
|
9295
|
+
"tags": ["agenr", "openclaw"],
|
|
9296
|
+
"canonical_key": "agenr-openclaw-ingestion-active"
|
|
9297
|
+
}
|
|
9298
|
+
|
|
9222
9299
|
### BORDERLINE \u2014 skip these
|
|
9223
9300
|
|
|
9224
9301
|
SKIP: "The assistant read the config file and found the port was 3000."
|
|
@@ -9264,6 +9341,84 @@ WHY: Routine execution. No durable knowledge, decisions, or lessons.
|
|
|
9264
9341
|
- source_context: one sentence, max 20 words.
|
|
9265
9342
|
- tags: 1-4 lowercase descriptive tags.
|
|
9266
9343
|
When related memories are injected before a chunk, they are reference material only. They do not lower the emission threshold.`;
|
|
9344
|
+
var OPENCLAW_CONFIDENCE_ADDENDUM = `
|
|
9345
|
+
## Confidence Language (role-labeled transcripts)
|
|
9346
|
+
|
|
9347
|
+
Transcript lines are prefixed with [user] or [assistant] role labels.
|
|
9348
|
+
Apply confidence-language analysis to [assistant] statements only.
|
|
9349
|
+
Never apply this logic to [user] messages.
|
|
9350
|
+
|
|
9351
|
+
### Verified agent statements - normal importance rules apply
|
|
9352
|
+
|
|
9353
|
+
An [assistant] statement is verified when it meets ANY of these conditions:
|
|
9354
|
+
- It immediately follows a line matching the pattern [called X: ...] such as
|
|
9355
|
+
[called exec: ...], [called web_search: ...], [called web_fetch: ...],
|
|
9356
|
+
[called Read: ...], [called write: ...], or any [called ...: ...] marker
|
|
9357
|
+
- It uses explicit verification language such as: "I confirmed", "I verified",
|
|
9358
|
+
"I checked and", "the output shows", "the test shows", "confirmed via",
|
|
9359
|
+
"the file shows", "I ran", "the result shows"
|
|
9360
|
+
- It is a direct summary of tool output returned in the same chunk
|
|
9361
|
+
|
|
9362
|
+
### Hedged agent statements - cap importance at 5 and add "unverified" tag
|
|
9363
|
+
|
|
9364
|
+
An [assistant] statement is hedged when it uses speculative language WITHOUT
|
|
9365
|
+
any of the verification signals above. Hedging indicators:
|
|
9366
|
+
- Uncertainty: "I think", "I believe", "I assume", "I'm not sure", "probably",
|
|
9367
|
+
"likely", "I don't know if", "I imagine", "I suspect"
|
|
9368
|
+
- Conditional: "it might be", "it could be", "possibly", "maybe"
|
|
9369
|
+
- Unverified recall: "I recall that", "if I remember correctly",
|
|
9370
|
+
"I seem to recall", "I think we"
|
|
9371
|
+
|
|
9372
|
+
When an [assistant] factual claim is hedged and unverified:
|
|
9373
|
+
- Set importance to min(your assigned importance, 5)
|
|
9374
|
+
- Add the tag "unverified"
|
|
9375
|
+
- Apply all other normal extraction rules unchanged
|
|
9376
|
+
|
|
9377
|
+
EXCEPTION: Do NOT cap agent recommendations or opinions. "I think we should
|
|
9378
|
+
use pnpm" is a recommendation, not a factual claim. Only cap FACTUAL CLAIMS
|
|
9379
|
+
made with hedging language. Ask: is the agent asserting a fact about the
|
|
9380
|
+
world, or expressing a preference or suggestion?
|
|
9381
|
+
|
|
9382
|
+
Do NOT apply any cap to [assistant] lines that ARE the tool call markers
|
|
9383
|
+
themselves (lines like "[called exec: ls]"). These are structural metadata,
|
|
9384
|
+
not agent assertions.
|
|
9385
|
+
|
|
9386
|
+
### Examples
|
|
9387
|
+
|
|
9388
|
+
HEDGED FACTUAL CLAIM - cap at 5, add "unverified":
|
|
9389
|
+
[m00010][assistant] I think agenr doesn't support OpenClaw ingestion yet.
|
|
9390
|
+
Result: importance capped at 5, tags include "unverified"
|
|
9391
|
+
|
|
9392
|
+
HEDGED FACTUAL CLAIM - cap at 5, add "unverified":
|
|
9393
|
+
[m00015][assistant] Probably the port is 3000 - I'm not 100% sure.
|
|
9394
|
+
Result: importance capped at 5, tags include "unverified"
|
|
9395
|
+
|
|
9396
|
+
VERIFIED after tool call - normal importance:
|
|
9397
|
+
[m00020][assistant] [called exec: agenr watch --list]
|
|
9398
|
+
[m00021][assistant] I confirmed via the output that OpenClaw ingestion is
|
|
9399
|
+
active on 3 files.
|
|
9400
|
+
Result: normal importance, no "unverified" tag
|
|
9401
|
+
|
|
9402
|
+
VERIFIED by explicit language - normal importance:
|
|
9403
|
+
[m00030][assistant] I verified in the source that the port is 4242.
|
|
9404
|
+
Result: normal importance
|
|
9405
|
+
|
|
9406
|
+
USER STATEMENT - never capped regardless of hedging language:
|
|
9407
|
+
[m00040][user] I'm not 100% sure, but I think our API key was rotated.
|
|
9408
|
+
Result: normal importance, no cap applied
|
|
9409
|
+
|
|
9410
|
+
RECOMMENDATION - not a factual claim, never capped:
|
|
9411
|
+
[m00050][assistant] I think we should switch to pnpm for consistency.
|
|
9412
|
+
Result: this is a suggestion, not a fact - do not apply the cap
|
|
9413
|
+
`;
|
|
9414
|
+
function buildExtractionSystemPrompt(platform) {
|
|
9415
|
+
if (platform === "openclaw") {
|
|
9416
|
+
return `${SYSTEM_PROMPT}
|
|
9417
|
+
|
|
9418
|
+
${OPENCLAW_CONFIDENCE_ADDENDUM}`;
|
|
9419
|
+
}
|
|
9420
|
+
return SYSTEM_PROMPT;
|
|
9421
|
+
}
|
|
9267
9422
|
var MAX_ATTEMPTS = 5;
|
|
9268
9423
|
var DEFAULT_INTER_CHUNK_DELAY_MS = 150;
|
|
9269
9424
|
var DEDUP_BATCH_SIZE = 50;
|
|
@@ -9525,6 +9680,12 @@ function validateEntry(entry) {
|
|
|
9525
9680
|
}
|
|
9526
9681
|
return null;
|
|
9527
9682
|
}
|
|
9683
|
+
function applyConfidenceCap(entry, platform) {
|
|
9684
|
+
if (platform === "openclaw" && entry.tags.includes("unverified") && entry.importance > 5) {
|
|
9685
|
+
return { ...entry, importance: 5 };
|
|
9686
|
+
}
|
|
9687
|
+
return entry;
|
|
9688
|
+
}
|
|
9528
9689
|
function selectStringField(record, ...keys) {
|
|
9529
9690
|
for (const key of keys) {
|
|
9530
9691
|
const value = record[key];
|
|
@@ -9990,7 +10151,7 @@ async function sleepMs2(ms) {
|
|
|
9990
10151
|
async function extractChunkOnce(params) {
|
|
9991
10152
|
const prompt = buildUserPrompt(params.chunk, params.related);
|
|
9992
10153
|
const context = {
|
|
9993
|
-
systemPrompt: SYSTEM_PROMPT,
|
|
10154
|
+
systemPrompt: params.systemPrompt ?? SYSTEM_PROMPT,
|
|
9994
10155
|
messages: [
|
|
9995
10156
|
{
|
|
9996
10157
|
role: "user",
|
|
@@ -10029,6 +10190,7 @@ async function extractChunkOnce(params) {
|
|
|
10029
10190
|
return { entries, warnings };
|
|
10030
10191
|
}
|
|
10031
10192
|
async function extractKnowledgeFromChunks(params) {
|
|
10193
|
+
const systemPrompt = buildExtractionSystemPrompt(params.platform);
|
|
10032
10194
|
const warnings = [];
|
|
10033
10195
|
const entries = [];
|
|
10034
10196
|
let successfulChunks = 0;
|
|
@@ -10087,6 +10249,7 @@ async function extractKnowledgeFromChunks(params) {
|
|
|
10087
10249
|
chunk,
|
|
10088
10250
|
model: params.client.resolvedModel.model,
|
|
10089
10251
|
apiKey: params.client.credentials.apiKey,
|
|
10252
|
+
systemPrompt,
|
|
10090
10253
|
verbose: params.verbose,
|
|
10091
10254
|
onVerbose: params.onVerbose,
|
|
10092
10255
|
onStreamDelta: bufferStreamDeltas ? (delta, kind) => {
|
|
@@ -10126,15 +10289,32 @@ async function extractKnowledgeFromChunks(params) {
|
|
|
10126
10289
|
);
|
|
10127
10290
|
} else if (chunkResult) {
|
|
10128
10291
|
dynamicDelay = Math.max(baseDelay, Math.floor(dynamicDelay * 0.9));
|
|
10292
|
+
const validatedChunkEntries = [];
|
|
10293
|
+
for (const entry of chunkResult.entries) {
|
|
10294
|
+
const capped = applyConfidenceCap(entry, params.platform);
|
|
10295
|
+
if (params.verbose && capped.importance !== entry.importance) {
|
|
10296
|
+
params.onVerbose?.(
|
|
10297
|
+
`[confidence-cap] lowered importance from ${entry.importance} to 5 (unverified tag)`
|
|
10298
|
+
);
|
|
10299
|
+
}
|
|
10300
|
+
const validationIssue = validateEntry(capped);
|
|
10301
|
+
if (validationIssue) {
|
|
10302
|
+
if (params.verbose) {
|
|
10303
|
+
params.onVerbose?.(`[entry-drop] ${validationIssue}`);
|
|
10304
|
+
}
|
|
10305
|
+
continue;
|
|
10306
|
+
}
|
|
10307
|
+
validatedChunkEntries.push(capped);
|
|
10308
|
+
}
|
|
10129
10309
|
if (params.onChunkComplete) {
|
|
10130
10310
|
await params.onChunkComplete({
|
|
10131
10311
|
chunkIndex: chunk.chunk_index,
|
|
10132
10312
|
totalChunks: params.chunks.length,
|
|
10133
|
-
entries:
|
|
10313
|
+
entries: validatedChunkEntries,
|
|
10134
10314
|
warnings: chunkResult.warnings
|
|
10135
10315
|
});
|
|
10136
10316
|
} else {
|
|
10137
|
-
entries.push(...
|
|
10317
|
+
entries.push(...validatedChunkEntries);
|
|
10138
10318
|
}
|
|
10139
10319
|
}
|
|
10140
10320
|
if (params.onStreamDelta) {
|
|
@@ -12887,6 +13067,7 @@ async function runIngestCommand(inputPaths, options, deps) {
|
|
|
12887
13067
|
chunks: parsed.chunks,
|
|
12888
13068
|
client,
|
|
12889
13069
|
verbose: false,
|
|
13070
|
+
platform: platform ?? parsed.metadata?.platform ?? void 0,
|
|
12890
13071
|
llmConcurrency,
|
|
12891
13072
|
db: options.noPreFetch ? void 0 : db,
|
|
12892
13073
|
embeddingApiKey: options.noPreFetch ? void 0 : embeddingApiKey ?? void 0,
|
|
@@ -13803,6 +13984,10 @@ var TOOL_DEFINITIONS = [
|
|
|
13803
13984
|
type: "string",
|
|
13804
13985
|
description: "Only entries newer than this (ISO date or relative, e.g. 7d, 1m)."
|
|
13805
13986
|
},
|
|
13987
|
+
until: {
|
|
13988
|
+
type: "string",
|
|
13989
|
+
description: "Only entries created before this point in time (ISO date or relative, e.g. 7d = entries older than 7 days). Use with since for a date range: since sets the lower bound, until the upper bound."
|
|
13990
|
+
},
|
|
13806
13991
|
threshold: {
|
|
13807
13992
|
type: "number",
|
|
13808
13993
|
description: "Minimum relevance score from 0.0 to 1.0.",
|
|
@@ -14047,42 +14232,6 @@ function parseCsvProjects(input) {
|
|
|
14047
14232
|
}
|
|
14048
14233
|
return parsed;
|
|
14049
14234
|
}
|
|
14050
|
-
function parseSinceToIso(since, now) {
|
|
14051
|
-
if (!since) {
|
|
14052
|
-
return void 0;
|
|
14053
|
-
}
|
|
14054
|
-
const trimmed = since.trim();
|
|
14055
|
-
if (!trimmed) {
|
|
14056
|
-
return void 0;
|
|
14057
|
-
}
|
|
14058
|
-
const durationMatch = trimmed.match(/^(\d+)\s*([hdmy])$/i);
|
|
14059
|
-
if (durationMatch) {
|
|
14060
|
-
const amount = Number(durationMatch[1]);
|
|
14061
|
-
const unit = durationMatch[2]?.toLowerCase();
|
|
14062
|
-
if (!Number.isFinite(amount) || amount <= 0 || !unit) {
|
|
14063
|
-
throw new RpcError(JSON_RPC_INVALID_PARAMS, "Invalid since value");
|
|
14064
|
-
}
|
|
14065
|
-
let multiplier = 0;
|
|
14066
|
-
if (unit === "h") {
|
|
14067
|
-
multiplier = 1e3 * 60 * 60;
|
|
14068
|
-
} else if (unit === "d") {
|
|
14069
|
-
multiplier = 1e3 * 60 * 60 * 24;
|
|
14070
|
-
} else if (unit === "m") {
|
|
14071
|
-
multiplier = 1e3 * 60 * 60 * 24 * 30;
|
|
14072
|
-
} else if (unit === "y") {
|
|
14073
|
-
multiplier = 1e3 * 60 * 60 * 24 * 365;
|
|
14074
|
-
}
|
|
14075
|
-
if (multiplier <= 0) {
|
|
14076
|
-
throw new RpcError(JSON_RPC_INVALID_PARAMS, "Invalid since value");
|
|
14077
|
-
}
|
|
14078
|
-
return new Date(now.getTime() - amount * multiplier).toISOString();
|
|
14079
|
-
}
|
|
14080
|
-
const parsedDate = new Date(trimmed);
|
|
14081
|
-
if (Number.isNaN(parsedDate.getTime())) {
|
|
14082
|
-
throw new RpcError(JSON_RPC_INVALID_PARAMS, "Invalid since value");
|
|
14083
|
-
}
|
|
14084
|
-
return parsedDate.toISOString();
|
|
14085
|
-
}
|
|
14086
14235
|
function normalizeTags3(value) {
|
|
14087
14236
|
if (!Array.isArray(value)) {
|
|
14088
14237
|
return [];
|
|
@@ -14359,7 +14508,22 @@ function createMcpServer(options = {}, deps = {}) {
|
|
|
14359
14508
|
const threshold = parseThreshold(args.threshold);
|
|
14360
14509
|
const now = resolvedDeps.nowFn();
|
|
14361
14510
|
const types = typeof args.types === "string" && args.types.trim().length > 0 ? parseCsvTypes(args.types) : void 0;
|
|
14362
|
-
|
|
14511
|
+
let since;
|
|
14512
|
+
if (typeof args.since === "string" && args.since.trim().length > 0) {
|
|
14513
|
+
try {
|
|
14514
|
+
since = parseSinceToIso(args.since, now);
|
|
14515
|
+
} catch {
|
|
14516
|
+
throw new RpcError(JSON_RPC_INVALID_PARAMS, "Invalid since value");
|
|
14517
|
+
}
|
|
14518
|
+
}
|
|
14519
|
+
let until;
|
|
14520
|
+
if (typeof args.until === "string" && args.until.trim().length > 0) {
|
|
14521
|
+
try {
|
|
14522
|
+
until = parseSinceToIso(args.until, now);
|
|
14523
|
+
} catch {
|
|
14524
|
+
throw new RpcError(JSON_RPC_INVALID_PARAMS, "Invalid until value");
|
|
14525
|
+
}
|
|
14526
|
+
}
|
|
14363
14527
|
const platformRaw = typeof args.platform === "string" ? args.platform.trim() : "";
|
|
14364
14528
|
const platform = platformRaw ? normalizeKnowledgePlatform(platformRaw) : null;
|
|
14365
14529
|
if (platformRaw && !platform) {
|
|
@@ -14397,6 +14561,7 @@ function createMcpServer(options = {}, deps = {}) {
|
|
|
14397
14561
|
limit,
|
|
14398
14562
|
types,
|
|
14399
14563
|
since,
|
|
14564
|
+
until,
|
|
14400
14565
|
platform: platform ?? void 0,
|
|
14401
14566
|
project,
|
|
14402
14567
|
projectStrict: projectStrict ? true : void 0,
|
|
@@ -14417,6 +14582,7 @@ function createMcpServer(options = {}, deps = {}) {
|
|
|
14417
14582
|
limit,
|
|
14418
14583
|
types,
|
|
14419
14584
|
since,
|
|
14585
|
+
until,
|
|
14420
14586
|
platform: platform ?? void 0,
|
|
14421
14587
|
project,
|
|
14422
14588
|
projectStrict: projectStrict ? true : void 0
|
|
@@ -14807,40 +14973,6 @@ function parseCsv(input) {
|
|
|
14807
14973
|
)
|
|
14808
14974
|
);
|
|
14809
14975
|
}
|
|
14810
|
-
function parseSinceToIso2(since, now = /* @__PURE__ */ new Date()) {
|
|
14811
|
-
if (!since) {
|
|
14812
|
-
return void 0;
|
|
14813
|
-
}
|
|
14814
|
-
const trimmed = since.trim();
|
|
14815
|
-
if (!trimmed) {
|
|
14816
|
-
return void 0;
|
|
14817
|
-
}
|
|
14818
|
-
const duration = trimmed.match(/^(\d+)\s*([hdy])$/i);
|
|
14819
|
-
if (duration) {
|
|
14820
|
-
const amount = Number(duration[1]);
|
|
14821
|
-
const unit = duration[2]?.toLowerCase();
|
|
14822
|
-
if (!Number.isFinite(amount) || amount <= 0 || !unit) {
|
|
14823
|
-
throw new Error("Invalid --since duration. Use 1h, 7d, 30d, or 1y.");
|
|
14824
|
-
}
|
|
14825
|
-
let millis = 0;
|
|
14826
|
-
if (unit === "h") {
|
|
14827
|
-
millis = amount * 60 * 60 * 1e3;
|
|
14828
|
-
} else if (unit === "d") {
|
|
14829
|
-
millis = amount * 24 * 60 * 60 * 1e3;
|
|
14830
|
-
} else if (unit === "y") {
|
|
14831
|
-
millis = amount * 365 * 24 * 60 * 60 * 1e3;
|
|
14832
|
-
}
|
|
14833
|
-
if (millis <= 0) {
|
|
14834
|
-
throw new Error("Invalid --since duration. Use 1h, 7d, 30d, or 1y.");
|
|
14835
|
-
}
|
|
14836
|
-
return new Date(now.getTime() - millis).toISOString();
|
|
14837
|
-
}
|
|
14838
|
-
const parsed = new Date(trimmed);
|
|
14839
|
-
if (Number.isNaN(parsed.getTime())) {
|
|
14840
|
-
throw new Error("Invalid --since value. Use 1h, 7d, 30d, 1y, or an ISO date.");
|
|
14841
|
-
}
|
|
14842
|
-
return parsed.toISOString();
|
|
14843
|
-
}
|
|
14844
14976
|
function stripEmbedding(result) {
|
|
14845
14977
|
const { embedding, ...entryWithoutEmbedding } = result.entry;
|
|
14846
14978
|
return {
|
|
@@ -15009,7 +15141,8 @@ async function runRecallCommand(queryInput, options, deps) {
|
|
|
15009
15141
|
const project = parsedProject.length > 0 ? parsedProject : void 0;
|
|
15010
15142
|
const excludeProject = parsedExcludeProject.length > 0 ? parsedExcludeProject : void 0;
|
|
15011
15143
|
const projectStrict = options.strict === true && Boolean(project && project.length > 0);
|
|
15012
|
-
const sinceIso =
|
|
15144
|
+
const sinceIso = parseSinceToIso(options.since, now, "Invalid --since value. Use 1h, 7d, 1m, 1y, or an ISO date.");
|
|
15145
|
+
const untilIso = parseSinceToIso(options.until, now, "Invalid --until value. Use 1h, 7d, 1m, 1y, or an ISO date.");
|
|
15013
15146
|
const queryForRecall = {
|
|
15014
15147
|
text: queryText ? shapeRecallText(queryText, context) : void 0,
|
|
15015
15148
|
limit,
|
|
@@ -15017,6 +15150,7 @@ async function runRecallCommand(queryInput, options, deps) {
|
|
|
15017
15150
|
tags: tags.length > 0 ? tags : void 0,
|
|
15018
15151
|
minImportance,
|
|
15019
15152
|
since: sinceIso,
|
|
15153
|
+
until: untilIso,
|
|
15020
15154
|
expiry,
|
|
15021
15155
|
scope: scope ?? "private",
|
|
15022
15156
|
platform,
|
|
@@ -16091,6 +16225,7 @@ async function runWatcher(options, deps) {
|
|
|
16091
16225
|
chunks: parsed.chunks,
|
|
16092
16226
|
client,
|
|
16093
16227
|
verbose: options.verbose,
|
|
16228
|
+
platform: currentPlatform && currentPlatform !== "mtime" ? normalizeKnowledgePlatform(currentPlatform) ?? void 0 : void 0,
|
|
16094
16229
|
db: options.noPreFetch ? void 0 : db ?? void 0,
|
|
16095
16230
|
embeddingApiKey: options.noPreFetch ? void 0 : embeddingApiKey ?? void 0,
|
|
16096
16231
|
noPreFetch: options.noPreFetch === true,
|
|
@@ -17456,13 +17591,14 @@ function createProgram() {
|
|
|
17456
17591
|
process.exitCode = result.exitCode;
|
|
17457
17592
|
}
|
|
17458
17593
|
);
|
|
17459
|
-
program.command("recall").description("Recall knowledge from the local database").argument("[query]", "Natural language query").option("--limit <n>", "Maximum number of results", parseIntOption, 10).option("--type <types>", "Filter by comma-separated entry types").option("--tags <tags>", "Filter by comma-separated tags").option("--min-importance <n>", "Minimum importance: 1-10").option("--since <duration>", "Filter by recency (1h, 7d, 30d, 1y) or ISO timestamp").option("--expiry <level>", "Filter by expiry: core|permanent|temporary").option("--platform <name>", "Filter by platform: openclaw, claude-code, codex").option("--project <name>", "Filter by project (repeatable)", (val, prev) => [...prev, val], []).option("--exclude-project <name>", "Exclude entries from project (repeatable)", (val, prev) => [...prev, val], []).option("--strict", "Exclude NULL-project entries from results", false).option("--json", "Output JSON", false).option("--db <path>", "Database path override").option("--budget <tokens>", "Approximate token budget", parseIntOption).option("--context <mode>", "Context mode: default|session-start|topic:<query>", "default").option("--scope <level>", "Visibility scope: private|personal|public", "private").option("--no-boost", "Disable scoring boosts and use raw vector similarity", false).option("--no-update", "Do not increment recall metadata", false).action(async (query, opts) => {
|
|
17594
|
+
program.command("recall").description("Recall knowledge from the local database").argument("[query]", "Natural language query").option("--limit <n>", "Maximum number of results", parseIntOption, 10).option("--type <types>", "Filter by comma-separated entry types").option("--tags <tags>", "Filter by comma-separated tags").option("--min-importance <n>", "Minimum importance: 1-10").option("--since <duration>", "Filter by recency (1h, 7d, 30d, 1y) or ISO timestamp").option("--until <date>", "Only entries at or before this time (ISO date or relative, e.g. 7d, 1m)").option("--expiry <level>", "Filter by expiry: core|permanent|temporary").option("--platform <name>", "Filter by platform: openclaw, claude-code, codex").option("--project <name>", "Filter by project (repeatable)", (val, prev) => [...prev, val], []).option("--exclude-project <name>", "Exclude entries from project (repeatable)", (val, prev) => [...prev, val], []).option("--strict", "Exclude NULL-project entries from results", false).option("--json", "Output JSON", false).option("--db <path>", "Database path override").option("--budget <tokens>", "Approximate token budget", parseIntOption).option("--context <mode>", "Context mode: default|session-start|topic:<query>", "default").option("--scope <level>", "Visibility scope: private|personal|public", "private").option("--no-boost", "Disable scoring boosts and use raw vector similarity", false).option("--no-update", "Do not increment recall metadata", false).action(async (query, opts) => {
|
|
17460
17595
|
const result = await runRecallCommand(query, {
|
|
17461
17596
|
limit: opts.limit,
|
|
17462
17597
|
type: opts.type,
|
|
17463
17598
|
tags: opts.tags,
|
|
17464
17599
|
minImportance: opts.minImportance,
|
|
17465
17600
|
since: opts.since,
|
|
17601
|
+
until: opts.until,
|
|
17466
17602
|
expiry: opts.expiry,
|
|
17467
17603
|
platform: opts.platform,
|
|
17468
17604
|
project: opts.project,
|