bikky 0.4.1 → 0.4.3

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.
Files changed (50) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/CODE_OF_CONDUCT.md +1 -1
  3. package/CONTRIBUTING.md +1 -1
  4. package/README.md +23 -17
  5. package/SUPPORT.md +3 -2
  6. package/dist/config.d.ts +11 -1
  7. package/dist/config.js +88 -20
  8. package/dist/daemon/capture-policy.d.ts +0 -1
  9. package/dist/daemon/capture-policy.js +0 -1
  10. package/dist/daemon/consolidation.d.ts +2 -1
  11. package/dist/daemon/consolidation.js +28 -11
  12. package/dist/daemon/entity-typing.js +10 -0
  13. package/dist/daemon/episode-summary.d.ts +4 -0
  14. package/dist/daemon/episode-summary.js +39 -8
  15. package/dist/daemon/extraction.d.ts +1 -1
  16. package/dist/daemon/extraction.js +52 -17
  17. package/dist/daemon/qdrant.d.ts +32 -10
  18. package/dist/daemon/qdrant.js +177 -60
  19. package/dist/daemon/relations.d.ts +3 -3
  20. package/dist/daemon/relations.js +27 -15
  21. package/dist/daemon/session-index.d.ts +5 -0
  22. package/dist/daemon/session-index.js +36 -9
  23. package/dist/daemon/session-summary.d.ts +3 -0
  24. package/dist/daemon/session-summary.js +48 -15
  25. package/dist/daemon/staleness.js +2 -2
  26. package/dist/daemon/transcript-sources.js +3 -2
  27. package/dist/daemon/watcher.js +2 -0
  28. package/dist/daemon/workstream-summary.d.ts +4 -0
  29. package/dist/daemon/workstream-summary.js +58 -16
  30. package/dist/install.d.ts +11 -0
  31. package/dist/install.js +38 -0
  32. package/dist/llm/embedding/index.js +2 -1
  33. package/dist/llm/embedding/providers/openai.js +8 -2
  34. package/dist/llm/embedding/providers/portkey.js +9 -2
  35. package/dist/llm/inference/index.js +2 -1
  36. package/dist/llm/util.d.ts +12 -0
  37. package/dist/llm/util.js +18 -0
  38. package/dist/mcp/helpers.d.ts +5 -0
  39. package/dist/mcp/helpers.js +27 -3
  40. package/dist/mcp/taxonomy.js +12 -1
  41. package/dist/mcp/tools.js +161 -57
  42. package/dist/mcp/types.d.ts +12 -0
  43. package/dist/package-verifier.d.ts +19 -0
  44. package/dist/package-verifier.js +83 -0
  45. package/dist/provenance/origin.d.ts +57 -0
  46. package/dist/provenance/origin.js +254 -0
  47. package/docs/config/fully-hosted.md +33 -13
  48. package/docs/config/hosted-models.md +33 -13
  49. package/docs/configuration.md +23 -5
  50. package/package.json +6 -2
package/dist/mcp/tools.js CHANGED
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import crypto from "node:crypto";
5
5
  import { z } from "zod";
6
- import { STALENESS_DAYS, THRESHOLD_DUPLICATE, THRESHOLD_RELATED, QDRANT_INDEXES, categoryValues, categoryEnumDescription, domainValues, domainEnumDescription, kindValues, kindEnumDescription, memorySubtypeValues, memorySubtypeEnumDescription, sourceValues, sourceEnumDescription, DEFAULT_CATEGORY, DEFAULT_DOMAIN, DEFAULT_KIND, DEFAULT_SOURCE, categoryForMemorySubtype, layerForMemorySubtype, normalizeCategory, normalizeDomain, normalizeKind, validateMemorySubtype, } from "./taxonomy.js";
6
+ import { STALENESS_DAYS, THRESHOLD_DUPLICATE, THRESHOLD_RELATED, QDRANT_INDEXES, categoryValues, categoryEnumDescription, domainValues, domainEnumDescription, kindValues, kindEnumDescription, memorySubtypeValues, memorySubtypeEnumDescription, DEFAULT_CATEGORY, DEFAULT_DOMAIN, DEFAULT_KIND, categoryForMemorySubtype, layerForMemorySubtype, normalizeCategory, normalizeDomain, normalizeKind, validateMemorySubtype, } from "./taxonomy.js";
7
7
  import { contentHash, daysSince, lastActivityDate, computeCombinedScore, buildFilter, formatFact, structuredFact, MEMORY_RECALL_EXCLUDED_KINDS, } from "./helpers.js";
8
8
  import { ready, setupError, setReady, log, embed, getEmbeddingConfig, qdrantReq, ensureCollectionsAll, qdrantUpsert, qdrantSearch, qdrantScroll, qdrantSetPayload, qdrantGetPoints, rebuildPool, hasPool, listDestinations, resolveDest, findPointById, } from "./api.js";
9
9
  import { DestinationNotFoundError } from "../routing.js";
@@ -11,7 +11,7 @@ import { saveConfig, loadConfig, EXTRACTION_HEALTH_PATH } from "../config.js";
11
11
  import { availableSearchScopes, resolveSearchScope, SearchScopeNotFoundError, } from "../search-scope.js";
12
12
  import { existsSync, readFileSync } from "node:fs";
13
13
  import { inspectWatcherPaths, formatIssue, repairSuspiciousWatcherPaths } from "../daemon/watcher-health.js";
14
- import { normalizeActorId, resolveActorIdentity } from "../provenance/actor.js";
14
+ import { buildOperationOrigin } from "../provenance/origin.js";
15
15
  import { addRedactionPayload, combineRedactions, redactStorageText, } from "../privacy/redaction.js";
16
16
  // ---------------------------------------------------------------------------
17
17
  // Runtime state
@@ -126,21 +126,17 @@ function resolveSearchScopeOrError(args) {
126
126
  };
127
127
  }
128
128
  }
129
- // Add actor identity payload fields. Workspace was removed in v0.4.0 — physical
130
- // separation now happens via routing destinations (see routing.ts).
131
- function addActorPayload(payload, actor, actorIdOverride) {
132
- const actorId = actor?.actor_id ?? normalizeActorId(actorIdOverride);
133
- if (actorId)
134
- payload["actor_id"] = actorId;
135
- if (actor?.actor_label) {
136
- const metadata = payload["metadata"] && typeof payload["metadata"] === "object" && !Array.isArray(payload["metadata"])
137
- ? payload["metadata"]
138
- : {};
139
- metadata["actor_label"] = actor.actor_label;
140
- if (actor.source)
141
- metadata["actor_source"] = actor.source;
142
- payload["metadata"] = metadata;
143
- }
129
+ function mcpOrigin(input) {
130
+ return buildOperationOrigin({
131
+ interface: "mcp",
132
+ agentType: "coding_agent",
133
+ action: input.action,
134
+ tool: input.tool,
135
+ outcome: input.outcome,
136
+ config: loadConfig(),
137
+ cwd: process.cwd(),
138
+ metadata: input.metadata,
139
+ });
144
140
  }
145
141
  async function getPointForWrite(dest, factId) {
146
142
  const existing = await qdrantGetPoints(dest, [factId]);
@@ -524,14 +520,12 @@ export function registerTools(mcp) {
524
520
  memory_subtype: z.enum(memorySubtypeValues()).optional().describe(memorySubtypeEnumDescription()),
525
521
  workspace_id: z.string().optional().describe("[Removed in v0.4.0] No-op. Routing now uses destinations — see destination."),
526
522
  destination: z.string().optional().describe("Optional destination override. When set, routes to that destination by name. Hard-errors if no such destination exists. Omit to let routing rules in ~/.bikky/config.json decide based on cwd/entities/content/metadata."),
527
- actor_id: z.string().optional().describe("Stable actor/person/agent identity associated with this capture. Overrides identity config/env/Git-derived fallback for this write."),
528
523
  episode_id: z.string().optional().describe("Coherent activity-segment ID. Group facts captured during the same coherent task or transcript."),
529
524
  workstream_key: z.string().optional().describe("Durable continuity key for a long-running objective (survives across sessions)."),
530
525
  task_key: z.string().optional().describe("Task or issue key (e.g. GitHub issue number, JIRA key)."),
531
526
  repo: z.string().optional().describe("Repository or project surface this fact relates to (e.g. 'bikky-dev/bikky')."),
532
527
  branch: z.string().optional().describe("Branch or working surface (e.g. 'main', 'feat/x')."),
533
528
  review_status: z.enum(["candidate", "reviewed", "approved", "rejected"]).optional().describe("Review lifecycle status. candidate=auto-extracted (daemon), reviewed=human-checked, approved=human-confirmed, rejected=incorrect. Agents normally leave this unset."),
534
- source: z.enum(sourceValues()).default(DEFAULT_SOURCE).describe(sourceEnumDescription()),
535
529
  confidence: z.number().min(0).max(1).default(0.9).describe("How certain you are this fact is correct (0.0-1.0). Default 0.9. Lower (~0.6) for inferred or unverified facts."),
536
530
  importance: z.number().min(0).max(1).optional().describe("How important this fact is for future recall (0.0-1.0). Defaults to 0.5 if omitted. ≥0.8 surfaces in session briefings."),
537
531
  supersedes: z.string().optional().describe("ID of an existing fact that this one replaces. The old fact is marked superseded and excluded from recall. Use this when a fact is updated; use memory_forget when a fact was simply wrong."),
@@ -541,7 +535,7 @@ export function registerTools(mcp) {
541
535
  to: z.string().describe("Target entity (lowercase)."),
542
536
  }).optional().describe("Optional typed edge between two entities — created in the same call. Use this whenever the fact also expresses a relationship; no separate tool call needed."),
543
537
  metadata: z.record(z.string(), z.string()).optional().describe("Arbitrary key-value metadata. Stored with the fact and exact-match filterable via memory_recall.metadata_filter (all key/value pairs must match — AND logic)."),
544
- }, async ({ content, category, entities, domain, kind, memory_subtype, workspace_id: _workspace_id, destination, actor_id, episode_id, workstream_key, task_key, repo, branch, review_status, source, confidence, importance, supersedes, relation, metadata, }) => {
538
+ }, async ({ content, category, entities, domain, kind, memory_subtype, workspace_id: _workspace_id, destination, episode_id, workstream_key, task_key, repo, branch, review_status, confidence, importance, supersedes, relation, metadata, }) => {
545
539
  const guard = requireReady();
546
540
  if (guard)
547
541
  return guard;
@@ -556,7 +550,6 @@ export function registerTools(mcp) {
556
550
  if (resolved.error)
557
551
  return resolved.error;
558
552
  const dest = resolved.dest;
559
- const actor = resolveActorIdentity({ actorId: actor_id, config: loadConfig() });
560
553
  const normalizedKind = normalizeKind(kind);
561
554
  let normalizedSubtype = null;
562
555
  try {
@@ -599,6 +592,19 @@ export function registerTools(mcp) {
599
592
  type: redactedRelation.type.text,
600
593
  to: redactedRelation.to.text,
601
594
  } : null;
595
+ const createOrigin = mcpOrigin({
596
+ action: "create",
597
+ tool: "memory_store",
598
+ metadata: {
599
+ destination: dest.name,
600
+ category: normalizedCategory,
601
+ kind: normalizedKind,
602
+ ...(normalizedSubtype ? { memory_subtype: normalizedSubtype } : {}),
603
+ ...(task_key ? { task_key } : {}),
604
+ ...(repo ? { repo } : {}),
605
+ ...(branch ? { branch } : {}),
606
+ },
607
+ });
602
608
  // 1. Exact dedup via content hash
603
609
  try {
604
610
  const hashFilter = buildFilter({}) ?? { must: [] };
@@ -612,6 +618,12 @@ export function registerTools(mcp) {
612
618
  reinforcement_count: count,
613
619
  last_reinforced_at: now,
614
620
  updated_at: now,
621
+ last_operation_origin: mcpOrigin({
622
+ action: "reinforce",
623
+ tool: "memory_store",
624
+ outcome: "exact_duplicate",
625
+ metadata: { destination: dest.name, fact_id: point.id },
626
+ }),
615
627
  });
616
628
  return {
617
629
  content: [{ type: "text", text: JSON.stringify({
@@ -647,6 +659,12 @@ export function registerTools(mcp) {
647
659
  reinforcement_count: count,
648
660
  last_reinforced_at: now,
649
661
  updated_at: now,
662
+ last_operation_origin: mcpOrigin({
663
+ action: "reinforce",
664
+ tool: "memory_store",
665
+ outcome: "semantic_duplicate",
666
+ metadata: { destination: dest.name, fact_id: point.id, similarity: topScore },
667
+ }),
650
668
  });
651
669
  return {
652
670
  content: [{ type: "text", text: JSON.stringify({
@@ -700,6 +718,12 @@ export function registerTools(mcp) {
700
718
  await qdrantSetPayload(dest, [supersedes], {
701
719
  superseded_by: factId,
702
720
  superseded_at: now,
721
+ updated_at: now,
722
+ last_operation_origin: mcpOrigin({
723
+ action: "supersede",
724
+ tool: "memory_store",
725
+ metadata: { destination: dest.name, new_fact_id: factId },
726
+ }),
703
727
  });
704
728
  }
705
729
  catch (e) {
@@ -713,7 +737,7 @@ export function registerTools(mcp) {
713
737
  domain: normalizedDomain,
714
738
  kind: normalizedKind,
715
739
  entities: normalizedEntities,
716
- source,
740
+ origin: createOrigin,
717
741
  confidence,
718
742
  importance: importance ?? 0.5,
719
743
  content_hash: hash,
@@ -745,7 +769,6 @@ export function registerTools(mcp) {
745
769
  }
746
770
  if (review_status)
747
771
  payload["review_status"] = review_status;
748
- addActorPayload(payload, actor);
749
772
  addRedactionPayload(payload, factRedactionSummary);
750
773
  await qdrantUpsert(dest, factId, vector, payload);
751
774
  // 7. Insert relation point if provided
@@ -761,7 +784,15 @@ export function registerTools(mcp) {
761
784
  kind: "relation",
762
785
  layer: "memory_object",
763
786
  entities: [sanitizedRelation.from.toLowerCase(), sanitizedRelation.to.toLowerCase()],
764
- source,
787
+ origin: mcpOrigin({
788
+ action: "create",
789
+ tool: "memory_store",
790
+ metadata: {
791
+ destination: dest.name,
792
+ parent_fact_id: factId,
793
+ relation_type: sanitizedRelation.type.toLowerCase(),
794
+ },
795
+ }),
765
796
  confidence,
766
797
  content_hash: contentHash("relation", relContent),
767
798
  reinforcement_count: 1,
@@ -774,7 +805,6 @@ export function registerTools(mcp) {
774
805
  relation_type: sanitizedRelation.type.toLowerCase(),
775
806
  to_entity: sanitizedRelation.to.toLowerCase(),
776
807
  };
777
- addActorPayload(relPayload, actor);
778
808
  addRedactionPayload(relPayload, relationRedactionSummary);
779
809
  await qdrantUpsert(dest, relationId, relVector, relPayload);
780
810
  }
@@ -782,9 +812,8 @@ export function registerTools(mcp) {
782
812
  action: "inserted",
783
813
  fact_id: factId,
784
814
  destination: dest.name,
815
+ origin: createOrigin,
785
816
  };
786
- if (actor.actor_id)
787
- result["actor_id"] = actor.actor_id;
788
817
  if (relationId)
789
818
  result["relation_id"] = relationId;
790
819
  if (redactionSummary.redacted)
@@ -819,7 +848,9 @@ export function registerTools(mcp) {
819
848
  workspace_id: z.string().optional().describe("[Removed in v0.4.0] No-op."),
820
849
  destination: z.string().optional().describe("Optional legacy single-destination override. Do not combine with search_scope. Prefer search_scope for routed/all/list search."),
821
850
  search_scope: searchScopeSchema.describe("Optional read/search scope. Accepts 'routed', 'all', a destination name, a configured scope name, a comma-separated destination list, or an array of destination names. Omit to use config.default_search_scope."),
822
- actor_id: z.string().optional().describe("Filter to facts captured by or associated with this stable actor identity. Optional."),
851
+ origin_user_id: z.string().optional().describe("Filter to facts whose creation origin.user.id matches this value. Optional."),
852
+ origin_agent_id: z.string().optional().describe("Filter to facts whose creation origin.agent.id matches this value. Optional."),
853
+ origin_interface: z.enum(["mcp", "daemon", "ui", "api", "cli", "system"]).optional().describe("Filter to facts created through this origin interface. Optional."),
823
854
  include_legacy_workspace: z.boolean().optional().describe("[Removed in v0.4.0] No-op."),
824
855
  entity: z.string().optional().describe("Restrict to facts mentioning this entity (case-insensitive). For full entity context prefer memory_entity."),
825
856
  episode_id: z.string().optional().describe("Filter by coherent episode ID."),
@@ -834,13 +865,12 @@ export function registerTools(mcp) {
834
865
  graph_depth: z.number().optional().default(0).describe("Entity-graph traversal depth. 0 = vector search only (fast, default). 1 = also surface up to ceil(limit / 2) extra 1-hop entity-related facts (slower; use when the user asks 'what's connected to X?'). In JSON output these are returned separately as related."),
835
866
  output_format: z.enum(["text", "json"]).optional().default("text").describe("Response format. text = backward-compatible human-readable lines (default). json = parseable object with query, limit metadata, results, related, counts, and optional nudge."),
836
867
  metadata_filter: z.record(z.string(), z.string()).optional().describe("Exact-match filter on the metadata map stored with each fact. All key/value pairs must match (AND logic)."),
837
- }, async ({ query, category, domain, kind, memory_subtype, workspace_id: _workspace_id, destination, search_scope, actor_id, include_legacy_workspace: _include_legacy_workspace, entity, episode_id, workstream_key, task_key, repo, branch, review_status, since, until, limit, graph_depth, output_format, metadata_filter, }) => {
868
+ }, async ({ query, category, domain, kind, memory_subtype, workspace_id: _workspace_id, destination, search_scope, origin_user_id, origin_agent_id, origin_interface, include_legacy_workspace: _include_legacy_workspace, entity, episode_id, workstream_key, task_key, repo, branch, review_status, since, until, limit, graph_depth, output_format, metadata_filter, }) => {
838
869
  const guard = requireReady();
839
870
  if (guard)
840
871
  return guard;
841
872
  const requestedLimit = limit ?? MEMORY_RECALL_DEFAULT_LIMIT;
842
873
  const effectiveLimit = clampRecallLimit(limit);
843
- const actorFilter = resolveActorIdentity({ actorId: actor_id, useGitFallback: false });
844
874
  const scopeResolved = resolveSearchScopeOrError({
845
875
  destination,
846
876
  search_scope,
@@ -873,7 +903,9 @@ export function registerTools(mcp) {
873
903
  domain: domain ? normalizeDomain(domain) : undefined,
874
904
  kind: normalizedKind,
875
905
  memory_subtype: normalizedSubtype,
876
- actor_id: actorFilter.actor_id,
906
+ origin_user_id,
907
+ origin_agent_id,
908
+ origin_interface,
877
909
  entity,
878
910
  episode_id,
879
911
  workstream_key,
@@ -1239,6 +1271,11 @@ export function registerTools(mcp) {
1239
1271
  superseded_by: `forgotten:${redactedReason.text}`,
1240
1272
  superseded_at: now,
1241
1273
  updated_at: now,
1274
+ last_operation_origin: mcpOrigin({
1275
+ action: "forget",
1276
+ tool: "memory_forget",
1277
+ metadata: { destination: dest.name, fact_id },
1278
+ }),
1242
1279
  // Mark this fact's vector as a bad-exemplar centroid: future
1243
1280
  // candidates with high cosine similarity will be auto-flagged
1244
1281
  // for review. Forgotten facts keep their original vector — the
@@ -1284,6 +1321,11 @@ export function registerTools(mcp) {
1284
1321
  last_reinforced_at: now,
1285
1322
  verification_count: newCount,
1286
1323
  updated_at: now,
1324
+ last_operation_origin: mcpOrigin({
1325
+ action: "verify",
1326
+ tool: "memory_verify",
1327
+ metadata: { destination: dest.name, fact_id },
1328
+ }),
1287
1329
  });
1288
1330
  return {
1289
1331
  content: [{ type: "text", text: JSON.stringify({
@@ -1318,13 +1360,19 @@ export function registerTools(mcp) {
1318
1360
  if (!located)
1319
1361
  return notFoundResult(fact_id);
1320
1362
  const { dest, point } = located;
1321
- const actor = resolveActorIdentity({ config: loadConfig() });
1322
1363
  const currentCount = point.payload.useful_count ?? 0;
1323
1364
  const newCount = currentCount + 1;
1365
+ const feedbackOrigin = mcpOrigin({
1366
+ action: "feedback",
1367
+ tool: "memory_mark_useful",
1368
+ outcome: "useful",
1369
+ metadata: { destination: dest.name, fact_id },
1370
+ });
1324
1371
  await qdrantSetPayload(dest, [fact_id], {
1325
1372
  useful_count: newCount,
1326
1373
  last_useful_at: now,
1327
1374
  updated_at: now,
1375
+ last_operation_origin: feedbackOrigin,
1328
1376
  });
1329
1377
  // Write a telemetry feedback_event row so the signal is also visible
1330
1378
  // to aggregations and review tooling.
@@ -1341,7 +1389,7 @@ export function registerTools(mcp) {
1341
1389
  memory_subtype: "feedback_event",
1342
1390
  layer: "memory_object",
1343
1391
  entities: [],
1344
- source: "agent",
1392
+ origin: feedbackOrigin,
1345
1393
  confidence: 1.0,
1346
1394
  importance: 0.3,
1347
1395
  content_hash: contentHash("feedback_event", `${fact_id}:useful:${now}`),
@@ -1350,7 +1398,6 @@ export function registerTools(mcp) {
1350
1398
  created_at: now,
1351
1399
  updated_at: now,
1352
1400
  };
1353
- addActorPayload(eventPayload, actor);
1354
1401
  addRedactionPayload(eventPayload, redactedEvent);
1355
1402
  try {
1356
1403
  const eventVector = await embed(redactedEvent.text);
@@ -1393,7 +1440,16 @@ export function registerTools(mcp) {
1393
1440
  if (!located)
1394
1441
  return notFoundResult(fact_id);
1395
1442
  const { dest } = located;
1396
- const actor = resolveActorIdentity({ config: loadConfig() });
1443
+ const feedbackOrigin = mcpOrigin({
1444
+ action: "feedback",
1445
+ tool: "memory_report_outcome",
1446
+ outcome,
1447
+ metadata: { destination: dest.name, fact_id },
1448
+ });
1449
+ await qdrantSetPayload(dest, [fact_id], {
1450
+ updated_at: now,
1451
+ last_operation_origin: feedbackOrigin,
1452
+ });
1397
1453
  const eventId = newId();
1398
1454
  const eventContent = notes
1399
1455
  ? `Fact ${fact_id} outcome=${outcome}: ${notes}`
@@ -1407,7 +1463,7 @@ export function registerTools(mcp) {
1407
1463
  memory_subtype: "outcome_event",
1408
1464
  layer: "memory_object",
1409
1465
  entities: [],
1410
- source: "agent",
1466
+ origin: feedbackOrigin,
1411
1467
  confidence: 1.0,
1412
1468
  importance: outcome === "wrong" || outcome === "misleading" ? 0.6 : 0.3,
1413
1469
  content_hash: contentHash("outcome_event", `${fact_id}:${outcome}:${now}`),
@@ -1416,7 +1472,6 @@ export function registerTools(mcp) {
1416
1472
  created_at: now,
1417
1473
  updated_at: now,
1418
1474
  };
1419
- addActorPayload(eventPayload, actor);
1420
1475
  addRedactionPayload(eventPayload, redactedEvent);
1421
1476
  const eventVector = await embed(redactedEvent.text);
1422
1477
  await qdrantUpsert(dest, eventId, eventVector, eventPayload);
@@ -1437,7 +1492,7 @@ export function registerTools(mcp) {
1437
1492
  // ── memory_session_summary ──────────────────────────────────────────────
1438
1493
  mcp.tool("memory_session_summary", [
1439
1494
  "Persist a compact summary of the current session — what got done, what decisions were made, what's still open.",
1440
- "Stored as kind='summary', memory_subtype='session_index', source='agent'. Keep it short (target 30-80 words). Future sessions retrieve these via memory_recall to bootstrap context faster than re-reading the original transcript.",
1495
+ "Stored as kind='summary', memory_subtype='session_index' with canonical origin metadata. Keep it short (target 30-80 words). Future sessions retrieve these via memory_recall to bootstrap context faster than re-reading the original transcript.",
1441
1496
  "Call this near session close (or at major milestone boundaries) when the work is meaningful enough to want a future agent to inherit. Skip for trivial single-question sessions.",
1442
1497
  ].join(" "), {
1443
1498
  content: z.string().describe("The summary text. Atomic, self-contained, 30-80 words ideally. Should answer: what was the goal, what did we do, what remains?"),
@@ -1448,8 +1503,7 @@ export function registerTools(mcp) {
1448
1503
  repo: z.string().optional().describe("Repository or project surface this summary relates to."),
1449
1504
  workspace_id: z.string().optional().describe("[Removed in v0.4.0] No-op."),
1450
1505
  destination: z.string().optional().describe("Optional destination override. Omit to let routing rules decide."),
1451
- actor_id: z.string().optional().describe("Stable actor identity associated with this session summary. Overrides identity config/env/Git fallback."),
1452
- }, async ({ content, entities, episode_id, workstream_key, task_key, repo, workspace_id: _workspace_id, destination, actor_id }) => {
1506
+ }, async ({ content, entities, episode_id, workstream_key, task_key, repo, workspace_id: _workspace_id, destination }) => {
1453
1507
  const guard = requireReady();
1454
1508
  if (guard)
1455
1509
  return guard;
@@ -1464,11 +1518,21 @@ export function registerTools(mcp) {
1464
1518
  if (resolved.error)
1465
1519
  return resolved.error;
1466
1520
  const dest = resolved.dest;
1467
- const actor = resolveActorIdentity({ actorId: actor_id, config: loadConfig() });
1468
1521
  const normalizedEntities = (entities ?? []).map((e) => e.trim().toLowerCase()).filter(Boolean);
1469
1522
  const summaryId = newId();
1470
1523
  const redactedContent = redactStorageText(content);
1471
1524
  const vector = await embed(redactedContent.text);
1525
+ const origin = mcpOrigin({
1526
+ action: "create",
1527
+ tool: "memory_session_summary",
1528
+ metadata: {
1529
+ destination: dest.name,
1530
+ ...(episode_id ? { episode_id } : {}),
1531
+ ...(workstream_key ? { workstream_key } : {}),
1532
+ ...(task_key ? { task_key } : {}),
1533
+ ...(repo ? { repo } : {}),
1534
+ },
1535
+ });
1472
1536
  const payload = {
1473
1537
  content: redactedContent.text,
1474
1538
  category: categoryForMemorySubtype("session_index") ?? "system",
@@ -1477,7 +1541,7 @@ export function registerTools(mcp) {
1477
1541
  memory_subtype: "session_index",
1478
1542
  layer: layerForMemorySubtype("session_index") ?? "episode",
1479
1543
  entities: normalizedEntities,
1480
- source: "agent",
1544
+ origin,
1481
1545
  confidence: 0.9,
1482
1546
  importance: 0.6,
1483
1547
  content_hash: contentHash("summary", redactedContent.text),
@@ -1496,7 +1560,6 @@ export function registerTools(mcp) {
1496
1560
  payload["task_key"] = task_key;
1497
1561
  if (repo)
1498
1562
  payload["repo"] = repo;
1499
- addActorPayload(payload, actor);
1500
1563
  addRedactionPayload(payload, redactedContent);
1501
1564
  await qdrantUpsert(dest, summaryId, vector, payload);
1502
1565
  return {
@@ -1504,7 +1567,7 @@ export function registerTools(mcp) {
1504
1567
  status: "summary_stored",
1505
1568
  summary_id: summaryId,
1506
1569
  destination: dest.name,
1507
- actor_id: actor.actor_id,
1570
+ origin,
1508
1571
  }) }],
1509
1572
  };
1510
1573
  }
@@ -1515,7 +1578,7 @@ export function registerTools(mcp) {
1515
1578
  // ── memory_distill ──────────────────────────────────────────────────────
1516
1579
  mcp.tool("memory_distill", [
1517
1580
  "Persist a distilled convention — a reusable learning, pattern, or runbook synthesized from multiple prior memories.",
1518
- "Stored as kind='distilled', memory_subtype='convention', source='agent'. Use this when you've noticed a pattern across several prior facts/sessions that's worth surfacing as its own atomic learning. The new memory will rank above raw facts in semantic recall because distilled patterns are higher-signal.",
1581
+ "Stored as kind='distilled', memory_subtype='convention' with canonical origin metadata. Use this when you've noticed a pattern across several prior facts/sessions that's worth surfacing as its own atomic learning. The new memory will rank above raw facts in semantic recall because distilled patterns are higher-signal.",
1519
1582
  "Provide 'supersedes' if this distillation replaces an earlier convention. The original stays in storage but is excluded from recall.",
1520
1583
  ].join(" "), {
1521
1584
  content: z.string().describe("One-sentence reusable convention or pattern. Should be self-contained and applicable beyond a single situation."),
@@ -1525,8 +1588,7 @@ export function registerTools(mcp) {
1525
1588
  repo: z.string().optional().describe("Repository or project surface this learning applies to."),
1526
1589
  workspace_id: z.string().optional().describe("[Removed in v0.4.0] No-op."),
1527
1590
  destination: z.string().optional().describe("Optional destination override. Omit to let routing rules decide."),
1528
- actor_id: z.string().optional().describe("Stable actor identity associated with this distillation. Overrides identity config/env/Git fallback."),
1529
- }, async ({ content, entities, supersedes, task_key, repo, workspace_id: _workspace_id, destination, actor_id }) => {
1591
+ }, async ({ content, entities, supersedes, task_key, repo, workspace_id: _workspace_id, destination }) => {
1530
1592
  const guard = requireReady();
1531
1593
  if (guard)
1532
1594
  return guard;
@@ -1541,11 +1603,20 @@ export function registerTools(mcp) {
1541
1603
  if (resolved.error)
1542
1604
  return resolved.error;
1543
1605
  const dest = resolved.dest;
1544
- const actor = resolveActorIdentity({ actorId: actor_id, config: loadConfig() });
1545
1606
  const normalizedEntities = entities.map((e) => e.trim().toLowerCase()).filter(Boolean);
1546
1607
  const distilledId = newId();
1547
1608
  const redactedContent = redactStorageText(content);
1548
1609
  const vector = await embed(redactedContent.text);
1610
+ const origin = mcpOrigin({
1611
+ action: "create",
1612
+ tool: "memory_distill",
1613
+ metadata: {
1614
+ destination: dest.name,
1615
+ ...(task_key ? { task_key } : {}),
1616
+ ...(repo ? { repo } : {}),
1617
+ ...(supersedes ? { supersedes } : {}),
1618
+ },
1619
+ });
1549
1620
  if (supersedes) {
1550
1621
  // Supersede may live in a different destination — locate it.
1551
1622
  const located = await locatePoint(supersedes);
@@ -1554,6 +1625,12 @@ export function registerTools(mcp) {
1554
1625
  await qdrantSetPayload(located.dest, [supersedes], {
1555
1626
  superseded_by: distilledId,
1556
1627
  superseded_at: now,
1628
+ updated_at: now,
1629
+ last_operation_origin: mcpOrigin({
1630
+ action: "supersede",
1631
+ tool: "memory_distill",
1632
+ metadata: { destination: located.dest.name, new_fact_id: distilledId },
1633
+ }),
1557
1634
  });
1558
1635
  }
1559
1636
  const payload = {
@@ -1564,7 +1641,7 @@ export function registerTools(mcp) {
1564
1641
  memory_subtype: "convention",
1565
1642
  layer: layerForMemorySubtype("convention") ?? "domain",
1566
1643
  entities: normalizedEntities,
1567
- source: "agent",
1644
+ origin,
1568
1645
  confidence: 0.9,
1569
1646
  importance: 0.7,
1570
1647
  content_hash: contentHash("distilled", redactedContent.text),
@@ -1579,7 +1656,6 @@ export function registerTools(mcp) {
1579
1656
  payload["task_key"] = task_key;
1580
1657
  if (repo)
1581
1658
  payload["repo"] = repo;
1582
- addActorPayload(payload, actor);
1583
1659
  addRedactionPayload(payload, redactedContent);
1584
1660
  await qdrantUpsert(dest, distilledId, vector, payload);
1585
1661
  return {
@@ -1588,7 +1664,7 @@ export function registerTools(mcp) {
1588
1664
  distilled_id: distilledId,
1589
1665
  destination: dest.name,
1590
1666
  supersedes: supersedes ?? null,
1591
- actor_id: actor.actor_id,
1667
+ origin,
1592
1668
  }) }],
1593
1669
  };
1594
1670
  }
@@ -1598,8 +1674,8 @@ export function registerTools(mcp) {
1598
1674
  });
1599
1675
  // ── memory_review ───────────────────────────────────────────────────────
1600
1676
  mcp.tool("memory_review", [
1601
- "Triage facts that were captured automatically by Bikky (source='system').",
1602
- "Only useful when the daemon is running and capturing memories from logs/transcripts; otherwise this returns an empty list. Supports four actions: list (default — show recent system-captured facts), approve (mark verified), reject (mark superseded with reason), correct (replace with edited content as a new fact).",
1677
+ "Triage facts that were captured automatically by Bikky (origin.interface='daemon' or legacy source='system').",
1678
+ "Only useful when the daemon is running and capturing memories from logs/transcripts; otherwise this returns an empty list. Supports four actions: list (default — show recent daemon/system-captured facts), approve (mark verified), reject (mark superseded with reason), correct (replace with edited content as a new fact).",
1603
1679
  ].join(" "), {
1604
1680
  limit: z.number().optional().default(10).describe("Max facts to return when action=list (default 10)."),
1605
1681
  action: z.enum(["list", "approve", "reject", "correct"]).optional().default("list").describe("What to do. list = show recent system-captured facts (default). approve = confirm a fact is correct (bumps verification count). reject = mark a fact as wrong (requires 'reason'). correct = supersede with an edited version (requires 'corrected_content')."),
@@ -1616,7 +1692,14 @@ export function registerTools(mcp) {
1616
1692
  // List spans all destinations.
1617
1693
  const destinations = listDestinations();
1618
1694
  const allPoints = [];
1619
- const filter = { must: [{ key: "source", match: { any: ["system", "daemon"] } }] };
1695
+ const filter = {
1696
+ must: [],
1697
+ should: [
1698
+ { key: "origin.interface", match: { value: "daemon" } },
1699
+ { key: "origin.agent.type", match: { any: ["daemon", "system"] } },
1700
+ { key: "source", match: { any: ["system", "daemon"] } },
1701
+ ],
1702
+ };
1620
1703
  for (const dest of destinations) {
1621
1704
  try {
1622
1705
  const result = await qdrantScroll(dest, filter, (limit ?? 10) * 2);
@@ -1654,6 +1737,12 @@ export function registerTools(mcp) {
1654
1737
  last_verified_at: now,
1655
1738
  verification_count: currentCount + 1,
1656
1739
  updated_at: now,
1740
+ last_operation_origin: mcpOrigin({
1741
+ action: "review",
1742
+ tool: "memory_review",
1743
+ outcome: "approved",
1744
+ metadata: { destination: dest.name, fact_id },
1745
+ }),
1657
1746
  });
1658
1747
  return { content: [{ type: "text", text: JSON.stringify({ status: "approved", fact_id, destination: dest.name }) }] };
1659
1748
  }
@@ -1670,6 +1759,12 @@ export function registerTools(mcp) {
1670
1759
  superseded_by: `rejected:${redactedReason.text}`,
1671
1760
  superseded_at: now,
1672
1761
  updated_at: now,
1762
+ last_operation_origin: mcpOrigin({
1763
+ action: "review",
1764
+ tool: "memory_review",
1765
+ outcome: "rejected",
1766
+ metadata: { destination: dest.name, fact_id },
1767
+ }),
1673
1768
  });
1674
1769
  return { content: [{ type: "text", text: JSON.stringify({
1675
1770
  status: "rejected",
@@ -1689,11 +1784,15 @@ export function registerTools(mcp) {
1689
1784
  const { dest, point } = located;
1690
1785
  const origPayload = point.payload;
1691
1786
  const redactedCorrected = redactStorageText(corrected_content);
1692
- const actor = resolveActorIdentity({ config: loadConfig() });
1693
1787
  const vector = await embed(redactedCorrected.text);
1694
1788
  const correctedId = crypto.randomUUID();
1695
1789
  const origCategory = normalizeCategory(origPayload?.category ?? DEFAULT_CATEGORY);
1696
1790
  const hash = contentHash(origCategory, redactedCorrected.text);
1791
+ const correctOrigin = mcpOrigin({
1792
+ action: "correct",
1793
+ tool: "memory_review",
1794
+ metadata: { destination: dest.name, corrected_from: fact_id },
1795
+ });
1697
1796
  const correctedPayload = {
1698
1797
  content: redactedCorrected.text,
1699
1798
  category: origCategory,
@@ -1707,7 +1806,7 @@ export function registerTools(mcp) {
1707
1806
  ...(origPayload?.repo ? { repo: origPayload.repo } : {}),
1708
1807
  ...(origPayload?.branch ? { branch: origPayload.branch } : {}),
1709
1808
  entities: origPayload?.entities ?? [],
1710
- source: "user",
1809
+ origin: correctOrigin,
1711
1810
  confidence: 0.95,
1712
1811
  importance: origPayload?.importance ?? 0.5,
1713
1812
  content_hash: hash,
@@ -1719,13 +1818,18 @@ export function registerTools(mcp) {
1719
1818
  updated_at: now,
1720
1819
  metadata: { ...(origPayload?.metadata ?? {}), corrected_from: fact_id },
1721
1820
  };
1722
- addActorPayload(correctedPayload, actor);
1723
1821
  addRedactionPayload(correctedPayload, redactedCorrected);
1724
1822
  await qdrantUpsert(dest, correctedId, vector, correctedPayload);
1725
1823
  await qdrantSetPayload(dest, [fact_id], {
1726
1824
  superseded_by: correctedId,
1727
1825
  superseded_at: now,
1728
1826
  updated_at: now,
1827
+ last_operation_origin: mcpOrigin({
1828
+ action: "supersede",
1829
+ tool: "memory_review",
1830
+ outcome: "corrected",
1831
+ metadata: { destination: dest.name, new_fact_id: correctedId },
1832
+ }),
1729
1833
  });
1730
1834
  return { content: [{ type: "text", text: JSON.stringify({ status: "corrected", old_fact_id: fact_id, new_fact_id: correctedId, destination: dest.name }) }] };
1731
1835
  }
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Type definitions for the Memory MCP server.
3
3
  */
4
+ import type { OperationOrigin } from "../provenance/origin.js";
4
5
  export interface McpToolResult {
5
6
  [key: string]: unknown;
6
7
  content: Array<{
@@ -36,6 +37,9 @@ export interface QdrantFilterCondition {
36
37
  is_empty?: {
37
38
  key: string;
38
39
  };
40
+ must?: QdrantFilterCondition[];
41
+ should?: QdrantFilterCondition[];
42
+ must_not?: QdrantFilterCondition[];
39
43
  }
40
44
  export interface QdrantFilter {
41
45
  must: QdrantFilterCondition[];
@@ -67,8 +71,12 @@ export interface FactPayload {
67
71
  kind?: string;
68
72
  layer?: string | null;
69
73
  memory_subtype?: string | null;
74
+ origin?: OperationOrigin;
75
+ last_operation_origin?: OperationOrigin;
76
+ /** @deprecated Origin is canonical for new writes. */
70
77
  actor_id?: string;
71
78
  entities: string[];
79
+ /** @deprecated Origin is canonical for new writes. */
72
80
  source?: string;
73
81
  confidence: number;
74
82
  importance?: number;
@@ -140,6 +148,10 @@ export interface FilterParams {
140
148
  domain?: string;
141
149
  kind?: string;
142
150
  memory_subtype?: string;
151
+ origin_user_id?: string;
152
+ origin_agent_id?: string;
153
+ origin_interface?: string;
154
+ /** @deprecated Use origin_user_id / origin_agent_id. */
143
155
  actor_id?: string;
144
156
  entity?: string;
145
157
  session_id?: string;
@@ -0,0 +1,19 @@
1
+ export interface PackageVerifierPattern {
2
+ re: RegExp;
3
+ reason: string;
4
+ }
5
+ export interface PackageVerifierSpec {
6
+ name: string;
7
+ requiredPaths: string[];
8
+ }
9
+ export declare const TEXT_EXTENSIONS: Set<string>;
10
+ export declare const FORBIDDEN_PATH_PATTERNS: PackageVerifierPattern[];
11
+ export declare const FORBIDDEN_CONTENT_PATTERNS: PackageVerifierPattern[];
12
+ export declare function normalizePackPath(file: string): string;
13
+ export declare function findForbiddenPath(file: string, patterns?: ReadonlyArray<PackageVerifierPattern>): PackageVerifierPattern | null;
14
+ export declare function findForbiddenContent(content: string, patterns?: ReadonlyArray<PackageVerifierPattern>): PackageVerifierPattern | null;
15
+ export declare function assertPackageContents(pkg: PackageVerifierSpec, files: string[], patterns?: ReadonlyArray<PackageVerifierPattern>): void;
16
+ export declare function shouldScanTextByMetadata(file: string, size: number, textExtensions?: ReadonlySet<string>): boolean;
17
+ export declare function shouldScanText(file: string): boolean;
18
+ export declare function assertSafeTextContent(pkgName: string, relPath: string, content: string): void;
19
+ //# sourceMappingURL=package-verifier.d.ts.map