@tpsdev-ai/flair 0.44.4 → 0.44.5

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.
@@ -115,6 +115,30 @@ async function unwrap(value) {
115
115
  }
116
116
  return value;
117
117
  }
118
+ /**
119
+ * flair#1188 — remove the raw `embedding` vector from a record before it is
120
+ * returned over the MCP surface. A stored memory carries a 768-float
121
+ * `embedding` (the HNSW vector); inlined into a tool result that is thousands
122
+ * of noise tokens per record on chat connectors that have a fixed context
123
+ * budget, and the caller can never do anything useful with it. Returns a
124
+ * shallow copy WITHOUT `embedding` (never mutates the source record), and
125
+ * passes through anything that is not a plain record — null, primitives,
126
+ * arrays, and the `{ error, status }` shapes `unwrap` produces — untouched.
127
+ *
128
+ * `memory_search` already projects with an explicit select that omits
129
+ * `embedding` (resources/semantic-retrieval-core.ts's DEFAULT_SELECT), and
130
+ * `bootstrap` uses the same select-without-embedding pushdown, so this is only
131
+ * needed on the FULL-record read/write paths (memory_get, and the write
132
+ * responses that echo the stored row).
133
+ */
134
+ function stripEmbedding(value) {
135
+ if (!value || typeof value !== "object" || Array.isArray(value))
136
+ return value;
137
+ if (!("embedding" in value))
138
+ return value;
139
+ const { embedding, ...rest } = value;
140
+ return rest;
141
+ }
118
142
  // ── Tool implementations (thin wrappers over existing handlers) ──────────────
119
143
  //
120
144
  // Each takes the resolved agent + the parsed tool arguments and returns a plain
@@ -190,7 +214,10 @@ async function memoryStore(agent, args) {
190
214
  }
191
215
  body.visibility = args.visibility;
192
216
  }
193
- return unwrap(await h.post(body));
217
+ // flair#1188 — memory_store's response goes through the same buildWriteResponse
218
+ // echo as memory_update; strip the server-regenerated embedding so no write
219
+ // tool ever inlines the vector. No-op when the response carries none.
220
+ return stripEmbedding(await unwrap(await h.post(body)));
194
221
  }
195
222
  /**
196
223
  * memory_update — id-targeted, dedup-BYPASSED overwrite/version path (memory-
@@ -240,6 +267,20 @@ async function memoryUpdate(agent, args) {
240
267
  delete record.validFrom;
241
268
  delete record.validTo;
242
269
  delete record.archivedAt;
270
+ // flair#1189 — retrievalCount and lastRetrieved are RECORD-scoped, not
271
+ // lineage-scoped: a brand-new successor record has no retrieval history of
272
+ // its OWN, so it must start with none. Inheriting them from the superseded
273
+ // record via the `...existing` spread produced a successor whose
274
+ // lastRetrieved PREDATED its own createdAt ("retrieved 8h before it
275
+ // existed"), silently corrupting any recency/usage-based ranking that reads
276
+ // these fields. Reset both here, at succession construction — NOT server-
277
+ // side, because `supersedes` is a PERMANENT property of every successor and
278
+ // legitimate later retrievalCount bumps route through put() on a record that
279
+ // still carries it. Usage/citation-ledger counters (usageCount, the #1147
280
+ // citation ledger) are a SEPARATE, arguably lineage-scoped question and are
281
+ // deliberately left untouched here (#1147's usage loop is currently inert).
282
+ record.retrievalCount = 0;
283
+ delete record.lastRetrieved;
243
284
  // flair#718 authorship-provenance — see memoryStore's comment: forward
244
285
  // the resolved OAuth client_id (never forgeable via args) so the NEW
245
286
  // version's provenance records which client authored this update.
@@ -247,7 +288,10 @@ async function memoryUpdate(agent, args) {
247
288
  record.claimedClient = agent.clientId;
248
289
  // A create needs a COLLECTION-bound instance (see resources/in-process.ts).
249
290
  const coll = await collectionResource(Cls, delegationContext(agent));
250
- return unwrap(await coll.post(record));
291
+ // flair#1188 — the write response echoes the stored row (Memory.post
292
+ // regenerates the embedding server-side), so strip the vector before it
293
+ // returns over the MCP surface. No-op when the response carries none.
294
+ return stripEmbedding(await unwrap(await coll.post(record)));
251
295
  }
252
296
  const merged = { ...existing, content, updatedAt: new Date().toISOString() };
253
297
  delete merged.embedding;
@@ -264,7 +308,9 @@ async function memoryUpdate(agent, args) {
264
308
  // the same unloaded-instance defect on the write. The static form loads the
265
309
  // row by `merged.id` and threads the context, then dispatches through
266
310
  // Memory.put()'s own ownership gate — no scope change, same as the read.
267
- return unwrap(await Cls.put(merged, delegationContext(agent)));
311
+ // flair#1188 — strip the embedding from the echoed write response (Memory.put
312
+ // regenerates the vector server-side); no-op when the response carries none.
313
+ return stripEmbedding(await unwrap(await Cls.put(merged, delegationContext(agent))));
268
314
  }
269
315
  async function memoryGet(agent, args) {
270
316
  const Cls = await handler("Memory");
@@ -286,7 +332,13 @@ async function memoryGet(agent, args) {
286
332
  // a plain `{ id, includeTrust }` property — Memory.get()'s wantsTrust() reads
287
333
  // it there (the in-process shape alongside the HTTP query-param shape).
288
334
  const target = args?.includeTrust === true ? { id: args?.id, includeTrust: true } : args?.id;
289
- return unwrap(await Cls.get(target, delegationContext(agent)));
335
+ const result = await unwrap(await Cls.get(target, delegationContext(agent)));
336
+ // flair#1188 — a by-id get loads the FULL record, including the 768-float
337
+ // `embedding` vector (search/bootstrap project it out; a raw get does not).
338
+ // Strip it by default so a chat connector isn't flooded with thousands of
339
+ // useless tokens per record; return it only when the caller explicitly opts
340
+ // in via includeEmbedding.
341
+ return args?.includeEmbedding === true ? result : stripEmbedding(result);
290
342
  }
291
343
  async function memoryDelete(agent, args) {
292
344
  const Cls = await handler("Memory");
@@ -514,13 +566,14 @@ export const TOOLS = {
514
566
  memory_get: {
515
567
  def: {
516
568
  name: "memory_get",
517
- description: "Retrieve a specific memory by ID.",
569
+ description: "Retrieve a specific memory by ID. The record's raw embedding vector is omitted by default (it is large and not useful to a caller); pass includeEmbedding=true to include it.",
518
570
  annotations: { readOnlyHint: true },
519
571
  inputSchema: {
520
572
  type: "object",
521
573
  properties: {
522
574
  id: { type: "string", description: "Memory ID" },
523
575
  includeTrust: { type: "boolean", description: "Attach a trust-evidence block (provenance, author, usage, freshness, supersession) to the record. Default false." },
576
+ includeEmbedding: { type: "boolean", description: "Include the raw embedding vector (hundreds of floats) in the returned record. Omitted by default because it is large and rarely useful to a caller. Default false." },
524
577
  },
525
578
  required: ["id"],
526
579
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tpsdev-ai/flair",
3
- "version": "0.44.4",
3
+ "version": "0.44.5",
4
4
  "packageManager": "bun@1.3.10",
5
5
  "description": "Identity, memory, and soul for AI agents. Cryptographic identity (Ed25519), semantic memory with local embeddings, and persistent personality — all in a single process.",
6
6
  "type": "module",