diff --git a/src/memory-runtime.ts b/src/memory-runtime.ts index 37d034be..3d72492d 100644 --- a/src/memory-runtime.ts +++ b/src/memory-runtime.ts @@ -131,11 +131,22 @@ function createMemorySearchManager( const legacyResults = filteredResults.map((item) => { const meta = parseMetadataJson(item); const text = resolveSearchResultText(item, meta); - return { + const kind = typeof meta.memory_kind === "string" ? meta.memory_kind : undefined; + const signals = meta.memory_signals as string[] | undefined; + const causedBy = stringArrayFromMeta(meta, "why_ids"); + const leadsTo = stringArrayFromMeta(meta, "how_ids"); + const timestamp = metaTimestamp(meta); + const enriched: Record = { ...item, text, content: text, }; + if (kind) enriched.kind = kind; + if (signals && signals.length > 0) enriched.signals = signals; + if (causedBy && causedBy.length > 0) enriched.caused_by = causedBy; + if (leadsTo && leadsTo.length > 0) enriched.leads_to = leadsTo; + if (timestamp) enriched.timestamp = timestamp; + return enriched; }); if (legacyCall) { return { results: legacyResults }; @@ -306,7 +317,12 @@ function toMemorySearchResult( text = resolveSearchResultText(item, meta), ) { const collection = typeof meta.collection === "string" ? meta.collection : "memory"; - return { + const kind = typeof meta.memory_kind === "string" ? meta.memory_kind : undefined; + const signals = meta.memory_signals as string[] | undefined; + const causedBy = stringArrayFromMeta(meta, "why_ids"); + const leadsTo = stringArrayFromMeta(meta, "how_ids"); + const timestamp = metaTimestamp(meta); + const result: Record = { path: encodeSearchResultPath(collection, item.id), startLine: 1, endLine: Math.max(1, text.split("\n").length), @@ -315,6 +331,43 @@ function toMemorySearchResult( source: collection.startsWith("session:") || collection.startsWith("session_") ? "sessions" : "memory", citation: `${collection}:${item.id}`, }; + if (kind) result.kind = kind; + if (signals && signals.length > 0) result.signals = signals; + if (causedBy && causedBy.length > 0) result.caused_by = causedBy; + if (leadsTo && leadsTo.length > 0) result.leads_to = leadsTo; + if (timestamp) result.timestamp = timestamp; + return result; +} + +function stringArrayFromMeta(meta: Record, key: string): string[] | undefined { + const raw = meta[key]; + if (Array.isArray(raw)) { + const filtered = raw.filter((v): v is string => typeof v === "string"); + return filtered.length > 0 ? filtered : undefined; + } + return undefined; +} + +function toSafeISOString(ms: number): string | undefined { + try { + const d = new Date(ms); + if (Number.isNaN(d.getTime())) return undefined; + return d.toISOString(); + } catch { + return undefined; + } +} + +function metaTimestamp(meta: Record): string | undefined { + const ts = meta.ts ?? meta.created_at ?? meta.ingested_at ?? meta.timestamp; + if (typeof ts === "number" && Number.isFinite(ts) && ts > 0) { + return toSafeISOString(ts); + } + if (typeof ts === "string" && ts.length > 0) { + const parsed = Date.parse(ts); + if (!Number.isNaN(parsed)) return toSafeISOString(parsed); + } + return undefined; } function encodeSearchResultPath(collection: string, id: string): string { diff --git a/src/memory-tools.ts b/src/memory-tools.ts index 6f17c816..073a3504 100644 --- a/src/memory-tools.ts +++ b/src/memory-tools.ts @@ -195,7 +195,14 @@ export function createLibraVdbMemoryTools( name: "memory_search", label: "Memory Search", description: - "Search LibraVDB durable memory and session recall for prior work, decisions, dates, people, preferences, todos, or history. Call once per user question — after receiving results, use them directly. Do not re-call in the same turn. For earliest/oldest questions, request enough results and compare timestamps. If disabled=true, memory is unavailable.", + "Search LibraVDB durable memory and session recall for prior work, decisions, dates, people, preferences, todos, or history. Call once per user question — after receiving results, use them directly. Do not re-call in the same turn.\n\n" + + "Result fields: Each hit returns a score, snippet, path, source, and citation. When present in metadata, additional fields are surfaced:\n" + + "- kind: cognitive kind of the memory (identity, fact, preference, constraint, decision, episode). Filter input with the kind parameter.\n" + + "- signals: cognitive signal bitmask (deontic, identity, preference, factual, temporal). Filter input with the signals parameter.\n" + + "- caused_by: array of upstream causal event IDs (why this happened). Use memory_get with these IDs to walk backward through the causal chain.\n" + + "- leads_to: array of downstream procedural event IDs (what this caused). Use memory_get with these IDs to walk forward through the event DAG.\n" + + "- timestamp: ISO 8601 UTC timestamp of when the memory was stored. Use to compare recency, establish temporal order, or answer 'when' questions.\n\n" + + "For earliest/oldest questions, request enough results and compare timestamps. If disabled=true, memory is unavailable.", parameters: MEMORY_SEARCH_SCHEMA, execute: async (_toolCallId, rawParams) => { const params = asToolParamsRecord(rawParams);