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.
- package/CHANGELOG.md +6 -0
- package/CODE_OF_CONDUCT.md +1 -1
- package/CONTRIBUTING.md +1 -1
- package/README.md +23 -17
- package/SUPPORT.md +3 -2
- package/dist/config.d.ts +11 -1
- package/dist/config.js +88 -20
- package/dist/daemon/capture-policy.d.ts +0 -1
- package/dist/daemon/capture-policy.js +0 -1
- package/dist/daemon/consolidation.d.ts +2 -1
- package/dist/daemon/consolidation.js +28 -11
- package/dist/daemon/entity-typing.js +10 -0
- package/dist/daemon/episode-summary.d.ts +4 -0
- package/dist/daemon/episode-summary.js +39 -8
- package/dist/daemon/extraction.d.ts +1 -1
- package/dist/daemon/extraction.js +52 -17
- package/dist/daemon/qdrant.d.ts +32 -10
- package/dist/daemon/qdrant.js +177 -60
- package/dist/daemon/relations.d.ts +3 -3
- package/dist/daemon/relations.js +27 -15
- package/dist/daemon/session-index.d.ts +5 -0
- package/dist/daemon/session-index.js +36 -9
- package/dist/daemon/session-summary.d.ts +3 -0
- package/dist/daemon/session-summary.js +48 -15
- package/dist/daemon/staleness.js +2 -2
- package/dist/daemon/transcript-sources.js +3 -2
- package/dist/daemon/watcher.js +2 -0
- package/dist/daemon/workstream-summary.d.ts +4 -0
- package/dist/daemon/workstream-summary.js +58 -16
- package/dist/install.d.ts +11 -0
- package/dist/install.js +38 -0
- package/dist/llm/embedding/index.js +2 -1
- package/dist/llm/embedding/providers/openai.js +8 -2
- package/dist/llm/embedding/providers/portkey.js +9 -2
- package/dist/llm/inference/index.js +2 -1
- package/dist/llm/util.d.ts +12 -0
- package/dist/llm/util.js +18 -0
- package/dist/mcp/helpers.d.ts +5 -0
- package/dist/mcp/helpers.js +27 -3
- package/dist/mcp/taxonomy.js +12 -1
- package/dist/mcp/tools.js +161 -57
- package/dist/mcp/types.d.ts +12 -0
- package/dist/package-verifier.d.ts +19 -0
- package/dist/package-verifier.js +83 -0
- package/dist/provenance/origin.d.ts +57 -0
- package/dist/provenance/origin.js +254 -0
- package/docs/config/fully-hosted.md +33 -13
- package/docs/config/hosted-models.md +33 -13
- package/docs/configuration.md +23 -5
- 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,
|
|
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 {
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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'
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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'
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 = {
|
|
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
|
-
|
|
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
|
}
|
package/dist/mcp/types.d.ts
CHANGED
|
@@ -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
|