exovault-mcp-server 1.4.1 → 1.5.0
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/dist/db.d.ts +47 -2
- package/dist/db.js +75 -10
- package/dist/default-documents.d.ts +22 -0
- package/dist/default-documents.js +261 -0
- package/dist/extraction-prompt.js +4 -4
- package/dist/gateway-client.d.ts +78 -0
- package/dist/gateway-client.js +102 -0
- package/dist/gateway-init.d.ts +1 -1
- package/dist/gateway-init.js +56 -6
- package/dist/graph-extraction.d.ts +44 -0
- package/dist/graph-extraction.js +211 -0
- package/dist/index.js +151 -1
- package/dist/rlm/verify.js +6 -6
- package/dist/rlm/writeback.js +10 -10
- package/dist/stopwords.d.ts +6 -0
- package/dist/stopwords.js +9 -0
- package/dist/tools/adversarial-context.d.ts +18 -0
- package/dist/tools/adversarial-context.js +45 -0
- package/dist/tools/agent-cards.d.ts +26 -0
- package/dist/tools/agent-cards.js +9 -0
- package/dist/tools/agent-pipelines.d.ts +37 -0
- package/dist/tools/agent-pipelines.js +9 -0
- package/dist/tools/contradiction-detection.d.ts +18 -0
- package/dist/tools/contradiction-detection.js +33 -0
- package/dist/tools/entity-extraction-utils.d.ts +9 -0
- package/dist/tools/entity-extraction-utils.js +35 -0
- package/dist/tools/explore-graph.d.ts +10 -0
- package/dist/tools/explore-graph.js +17 -0
- package/dist/tools/intent-detection.d.ts +8 -0
- package/dist/tools/intent-detection.js +50 -0
- package/dist/tools/knowledge-links.js +1 -0
- package/dist/tools/llm-call.d.ts +20 -0
- package/dist/tools/llm-call.js +44 -0
- package/dist/tools/llm-reranker.d.ts +39 -0
- package/dist/tools/llm-reranker.js +130 -0
- package/dist/tools/multi-step-retrieval.d.ts +28 -0
- package/dist/tools/multi-step-retrieval.js +39 -0
- package/dist/tools/pipeline-router.d.ts +32 -0
- package/dist/tools/pipeline-router.js +97 -0
- package/dist/tools/query-classifier.d.ts +43 -0
- package/dist/tools/query-classifier.js +321 -0
- package/dist/tools/search-memories.d.ts +4 -0
- package/dist/tools/search-memories.js +386 -21
- package/dist/tools/session-start.d.ts +1 -0
- package/dist/tools/session-start.js +66 -13
- package/dist/tools/synthesize.d.ts +27 -0
- package/dist/tools/synthesize.js +100 -0
- package/dist/tools/update-memory.js +2 -0
- package/dist/tools/write-memory.d.ts +10 -1
- package/dist/tools/write-memory.js +288 -19
- package/package.json +1 -1
package/dist/db.d.ts
CHANGED
|
@@ -62,6 +62,12 @@ export interface MemoryRow {
|
|
|
62
62
|
source_note_ids: string[] | null;
|
|
63
63
|
superseded_by_id: string | null;
|
|
64
64
|
entities: string[] | null;
|
|
65
|
+
entity_descriptions: Array<{
|
|
66
|
+
name: string;
|
|
67
|
+
type: string;
|
|
68
|
+
description: string;
|
|
69
|
+
}> | null;
|
|
70
|
+
community_id: number | null;
|
|
65
71
|
created_at: string;
|
|
66
72
|
updated_at: string;
|
|
67
73
|
}
|
|
@@ -125,6 +131,11 @@ export declare function insertMemory(supabase: SupabaseClient, data: {
|
|
|
125
131
|
source_note_ids?: string[] | null;
|
|
126
132
|
superseded_by_id?: string | null;
|
|
127
133
|
entities?: string[] | null;
|
|
134
|
+
entity_descriptions?: Array<{
|
|
135
|
+
name: string;
|
|
136
|
+
type: string;
|
|
137
|
+
description: string;
|
|
138
|
+
}> | null;
|
|
128
139
|
blind_tokens?: string[] | null;
|
|
129
140
|
indexing_status?: string;
|
|
130
141
|
}): Promise<MemoryRow>;
|
|
@@ -171,7 +182,7 @@ export interface MemoryEmbeddingMatch {
|
|
|
171
182
|
importance: number;
|
|
172
183
|
}
|
|
173
184
|
export declare function matchNoteEmbeddings(supabase: SupabaseClient, embedding: number[], userId: string, threshold?: number, count?: number): Promise<EmbeddingMatch[]>;
|
|
174
|
-
export declare function matchMemoryEmbeddings(supabase: SupabaseClient, embedding: number[], userId: string, threshold?: number, count?: number): Promise<MemoryEmbeddingMatch[]>;
|
|
185
|
+
export declare function matchMemoryEmbeddings(supabase: SupabaseClient, embedding: number[], userId: string, threshold?: number, count?: number, vaultIds?: string[] | null): Promise<MemoryEmbeddingMatch[]>;
|
|
175
186
|
export interface BlindTokenMatch {
|
|
176
187
|
memory_id: string;
|
|
177
188
|
match_count: number;
|
|
@@ -225,6 +236,16 @@ export interface MemoryHealthStats {
|
|
|
225
236
|
}
|
|
226
237
|
export declare function getMemoryHealthStats(supabase: SupabaseClient, userId: string, vaultId?: string): Promise<MemoryHealthStats>;
|
|
227
238
|
export declare function searchMemoriesByEntity(supabase: SupabaseClient, userId: string, entity: string, limit?: number): Promise<MemoryRow[]>;
|
|
239
|
+
/**
|
|
240
|
+
* Fetch memory IDs that belong to any of the given community clusters.
|
|
241
|
+
* Used by community routing signal in search.
|
|
242
|
+
*/
|
|
243
|
+
export declare function getMemoryIdsByCommunityIds(supabase: SupabaseClient, userId: string, communityIds: number[], limit?: number, vaultId?: string): Promise<string[]>;
|
|
244
|
+
/**
|
|
245
|
+
* Fetch community_ids for a set of memory IDs. Used to resolve
|
|
246
|
+
* which communities the entity signal hits belong to.
|
|
247
|
+
*/
|
|
248
|
+
export declare function getCommunityIdsForMemories(supabase: SupabaseClient, userId: string, memoryIds: string[]): Promise<number[]>;
|
|
228
249
|
export declare function getRelatedMemoriesById(supabase: SupabaseClient, userId: string, memoryId: string, limit?: number): Promise<{
|
|
229
250
|
source: MemoryRow | null;
|
|
230
251
|
related: MemoryRow[];
|
|
@@ -244,6 +265,7 @@ export interface KnowledgeLinkRow {
|
|
|
244
265
|
relation_type: LinkRelationType;
|
|
245
266
|
encrypted_label: string | null;
|
|
246
267
|
label_iv: string | null;
|
|
268
|
+
confidence: number;
|
|
247
269
|
is_pending: boolean;
|
|
248
270
|
pending_target_hash: string | null;
|
|
249
271
|
created_at: string;
|
|
@@ -258,6 +280,7 @@ export declare function insertKnowledgeLink(supabase: SupabaseClient, data: {
|
|
|
258
280
|
relation_type: LinkRelationType;
|
|
259
281
|
encrypted_label?: string | null;
|
|
260
282
|
label_iv?: string | null;
|
|
283
|
+
confidence?: number;
|
|
261
284
|
is_pending?: boolean;
|
|
262
285
|
pending_target_hash?: string | null;
|
|
263
286
|
created_by?: string | null;
|
|
@@ -278,10 +301,13 @@ export declare function bulkInsertKnowledgeLinks(supabase: SupabaseClient, links
|
|
|
278
301
|
relation_type: LinkRelationType;
|
|
279
302
|
encrypted_label?: string | null;
|
|
280
303
|
label_iv?: string | null;
|
|
304
|
+
confidence?: number;
|
|
281
305
|
is_pending?: boolean;
|
|
282
306
|
pending_target_hash?: string | null;
|
|
283
307
|
created_by?: string | null;
|
|
284
|
-
}>): Promise<
|
|
308
|
+
}>): Promise<Array<{
|
|
309
|
+
id: string;
|
|
310
|
+
}>>;
|
|
285
311
|
/**
|
|
286
312
|
* Resolves pending wiki-links by matching stored SHA-256 hashes against
|
|
287
313
|
* candidate note names. Caller provides a map of hash → noteId.
|
|
@@ -297,6 +323,25 @@ export declare function getKnowledgeGraph(supabase: SupabaseClient, userId: stri
|
|
|
297
323
|
from_id: string;
|
|
298
324
|
relation: LinkRelationType;
|
|
299
325
|
}>>;
|
|
326
|
+
export declare function insertRelationshipEmbedding(supabase: SupabaseClient, data: {
|
|
327
|
+
user_id: string;
|
|
328
|
+
link_id: string;
|
|
329
|
+
source_entity: string;
|
|
330
|
+
target_entity: string;
|
|
331
|
+
keywords: string;
|
|
332
|
+
encrypted_description?: string | null;
|
|
333
|
+
description_iv?: string | null;
|
|
334
|
+
embedding_model: string;
|
|
335
|
+
embedding: number[];
|
|
336
|
+
}): Promise<void>;
|
|
337
|
+
export declare function matchRelationshipEmbeddings(supabase: SupabaseClient, queryEmbedding: number[], userId: string, threshold?: number, matchCount?: number): Promise<Array<{
|
|
338
|
+
id: string;
|
|
339
|
+
link_id: string;
|
|
340
|
+
source_entity: string;
|
|
341
|
+
target_entity: string;
|
|
342
|
+
keywords: string;
|
|
343
|
+
similarity: number;
|
|
344
|
+
}>>;
|
|
300
345
|
export interface AgentMessageRow {
|
|
301
346
|
id: string;
|
|
302
347
|
user_id: string;
|
package/dist/db.js
CHANGED
|
@@ -2,7 +2,7 @@ import { sanitizeDbError } from "./error-sanitizer.js";
|
|
|
2
2
|
// ─── Column lists (explicit to avoid SELECT * egress overhead) ───────────────
|
|
3
3
|
const VAULT_COLUMNS = "id, user_id, encrypted_name, name_iv, icon, color, sort_order, created_at, updated_at";
|
|
4
4
|
const NOTE_COLUMNS = "id, user_id, vault_id, encrypted_title, title_iv, encrypted_content, content_iv, content_type, encrypted_tags, tags_iv, embedding_status, import_source, import_source_id, is_trashed, created_at, updated_at";
|
|
5
|
-
const LINK_COLUMNS = "id, user_id, source_type, source_id, target_type, target_id, relation_type, encrypted_label, label_iv, is_pending, pending_target_hash, created_at, created_by";
|
|
5
|
+
const LINK_COLUMNS = "id, user_id, source_type, source_id, target_type, target_id, relation_type, encrypted_label, label_iv, confidence, is_pending, pending_target_hash, created_at, created_by";
|
|
6
6
|
const MESSAGE_COLUMNS = "id, user_id, vault_id, sender_type, sender_id, target_type, target_id, category, priority, subject, encrypted_content, content_iv, status, delivered_at, acknowledged_at, parent_message_id, thread_id, expires_at, metadata, created_at, updated_at";
|
|
7
7
|
// ─── Query helpers ────────────────────────────────────────────────────────────
|
|
8
8
|
export async function getVaults(supabase, userId) {
|
|
@@ -288,21 +288,23 @@ export async function getEmbeddingsForMemories(supabase, userId, memoryIds) {
|
|
|
288
288
|
}
|
|
289
289
|
export async function matchNoteEmbeddings(supabase, embedding, userId, threshold = 0.5, count = 20) {
|
|
290
290
|
const { data, error } = await supabase.rpc("match_note_embeddings", {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
291
|
+
p_embedding: JSON.stringify(embedding),
|
|
292
|
+
p_match_threshold: threshold,
|
|
293
|
+
p_match_count: count,
|
|
294
294
|
p_user_id: userId,
|
|
295
|
+
p_vault_ids: null,
|
|
295
296
|
});
|
|
296
297
|
if (error)
|
|
297
298
|
throw new Error(sanitizeDbError("match note embeddings", error.message));
|
|
298
299
|
return (data ?? []);
|
|
299
300
|
}
|
|
300
|
-
export async function matchMemoryEmbeddings(supabase, embedding, userId, threshold = 0.5, count = 20) {
|
|
301
|
+
export async function matchMemoryEmbeddings(supabase, embedding, userId, threshold = 0.5, count = 20, vaultIds) {
|
|
301
302
|
const { data, error } = await supabase.rpc("match_memory_embeddings", {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
303
|
+
p_embedding: JSON.stringify(embedding),
|
|
304
|
+
p_match_threshold: threshold,
|
|
305
|
+
p_match_count: count,
|
|
305
306
|
p_user_id: userId,
|
|
307
|
+
p_vault_ids: vaultIds ?? null,
|
|
306
308
|
});
|
|
307
309
|
if (error)
|
|
308
310
|
throw new Error(sanitizeDbError("match memory embeddings", error.message));
|
|
@@ -321,6 +323,7 @@ export async function matchMemoriesByBlindTokens(supabase, tokenHashes, userId,
|
|
|
321
323
|
p_vault_id: vaultId ?? null,
|
|
322
324
|
p_match_count: count,
|
|
323
325
|
p_include_archived: includeArchived,
|
|
326
|
+
p_vault_ids: null,
|
|
324
327
|
});
|
|
325
328
|
if (error)
|
|
326
329
|
throw new Error(sanitizeDbError("match memories by blind tokens", error.message));
|
|
@@ -339,6 +342,7 @@ export async function matchNotesByBlindTokens(supabase, tokenHashes, userId, vau
|
|
|
339
342
|
p_vault_id: vaultId ?? null,
|
|
340
343
|
p_match_count: count,
|
|
341
344
|
p_include_trashed: includeTrashed,
|
|
345
|
+
p_vault_ids: null,
|
|
342
346
|
});
|
|
343
347
|
if (error)
|
|
344
348
|
throw new Error(sanitizeDbError("match notes by blind tokens", error.message));
|
|
@@ -503,6 +507,46 @@ export async function searchMemoriesByEntity(supabase, userId, entity, limit = 2
|
|
|
503
507
|
throw new Error(sanitizeDbError("search memories by entity", error.message));
|
|
504
508
|
return (data ?? []);
|
|
505
509
|
}
|
|
510
|
+
/**
|
|
511
|
+
* Fetch memory IDs that belong to any of the given community clusters.
|
|
512
|
+
* Used by community routing signal in search.
|
|
513
|
+
*/
|
|
514
|
+
export async function getMemoryIdsByCommunityIds(supabase, userId, communityIds, limit = 30, vaultId) {
|
|
515
|
+
if (communityIds.length === 0)
|
|
516
|
+
return [];
|
|
517
|
+
let query = supabase
|
|
518
|
+
.from("memories")
|
|
519
|
+
.select("id")
|
|
520
|
+
.eq("user_id", userId)
|
|
521
|
+
.eq("is_archived", false)
|
|
522
|
+
.in("community_id", communityIds);
|
|
523
|
+
if (vaultId) {
|
|
524
|
+
query = query.eq("vault_id", vaultId);
|
|
525
|
+
}
|
|
526
|
+
const { data, error } = await query
|
|
527
|
+
.order("updated_at", { ascending: false })
|
|
528
|
+
.limit(limit);
|
|
529
|
+
if (error)
|
|
530
|
+
throw new Error(sanitizeDbError("community routing", error.message));
|
|
531
|
+
return (data ?? []).map((r) => r.id);
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Fetch community_ids for a set of memory IDs. Used to resolve
|
|
535
|
+
* which communities the entity signal hits belong to.
|
|
536
|
+
*/
|
|
537
|
+
export async function getCommunityIdsForMemories(supabase, userId, memoryIds) {
|
|
538
|
+
if (memoryIds.length === 0)
|
|
539
|
+
return [];
|
|
540
|
+
const { data, error } = await supabase
|
|
541
|
+
.from("memories")
|
|
542
|
+
.select("community_id")
|
|
543
|
+
.eq("user_id", userId)
|
|
544
|
+
.in("id", memoryIds.slice(0, 50))
|
|
545
|
+
.not("community_id", "is", null);
|
|
546
|
+
if (error)
|
|
547
|
+
throw new Error(sanitizeDbError("community id lookup", error.message));
|
|
548
|
+
return [...new Set((data ?? []).map((r) => r.community_id))];
|
|
549
|
+
}
|
|
506
550
|
export async function getRelatedMemoriesById(supabase, userId, memoryId, limit = 20) {
|
|
507
551
|
// Fetch the source memory
|
|
508
552
|
const source = await getMemory(supabase, userId, memoryId);
|
|
@@ -591,6 +635,7 @@ export async function insertKnowledgeLink(supabase, data) {
|
|
|
591
635
|
encrypted_label: data.encrypted_label ?? null,
|
|
592
636
|
label_iv: data.label_iv ?? null,
|
|
593
637
|
is_pending: data.is_pending ?? false,
|
|
638
|
+
confidence: data.confidence ?? 0.8,
|
|
594
639
|
pending_target_hash: data.pending_target_hash ?? null,
|
|
595
640
|
created_by: data.created_by ?? null,
|
|
596
641
|
})
|
|
@@ -701,7 +746,7 @@ export async function deleteLinksForNode(supabase, userId, nodeType, nodeId) {
|
|
|
701
746
|
}
|
|
702
747
|
export async function bulkInsertKnowledgeLinks(supabase, links) {
|
|
703
748
|
if (links.length === 0)
|
|
704
|
-
return
|
|
749
|
+
return [];
|
|
705
750
|
const rows = links.map((l) => ({
|
|
706
751
|
user_id: l.user_id,
|
|
707
752
|
source_type: l.source_type,
|
|
@@ -711,6 +756,7 @@ export async function bulkInsertKnowledgeLinks(supabase, links) {
|
|
|
711
756
|
relation_type: l.relation_type,
|
|
712
757
|
encrypted_label: l.encrypted_label ?? null,
|
|
713
758
|
label_iv: l.label_iv ?? null,
|
|
759
|
+
confidence: l.confidence ?? 0.8,
|
|
714
760
|
is_pending: l.is_pending ?? false,
|
|
715
761
|
pending_target_hash: l.pending_target_hash ?? null,
|
|
716
762
|
created_by: l.created_by ?? null,
|
|
@@ -722,7 +768,7 @@ export async function bulkInsertKnowledgeLinks(supabase, links) {
|
|
|
722
768
|
.select("id");
|
|
723
769
|
if (error)
|
|
724
770
|
throw new Error(sanitizeDbError("bulk insert knowledge links", error.message));
|
|
725
|
-
return data
|
|
771
|
+
return (data ?? []);
|
|
726
772
|
}
|
|
727
773
|
/**
|
|
728
774
|
* Resolves pending wiki-links by matching stored SHA-256 hashes against
|
|
@@ -777,6 +823,25 @@ export async function getKnowledgeGraph(supabase, userId, nodeType, nodeId, maxH
|
|
|
777
823
|
throw new Error(sanitizeDbError("traverse knowledge graph", error.message));
|
|
778
824
|
return (data ?? []);
|
|
779
825
|
}
|
|
826
|
+
// ─── Relationship Embeddings ─────────────────────────────────────────────────
|
|
827
|
+
export async function insertRelationshipEmbedding(supabase, data) {
|
|
828
|
+
const { error } = await supabase
|
|
829
|
+
.from("relationship_embeddings")
|
|
830
|
+
.upsert(data, { onConflict: "user_id,link_id,embedding_model" });
|
|
831
|
+
if (error)
|
|
832
|
+
throw new Error(sanitizeDbError("upsert relationship embedding", error.message));
|
|
833
|
+
}
|
|
834
|
+
export async function matchRelationshipEmbeddings(supabase, queryEmbedding, userId, threshold = 0.4, matchCount = 10) {
|
|
835
|
+
const { data, error } = await supabase.rpc("match_relationship_embeddings", {
|
|
836
|
+
p_user_id: userId,
|
|
837
|
+
p_query_embedding: JSON.stringify(queryEmbedding),
|
|
838
|
+
p_match_threshold: threshold,
|
|
839
|
+
p_match_count: matchCount,
|
|
840
|
+
});
|
|
841
|
+
if (error)
|
|
842
|
+
throw new Error(sanitizeDbError("match relationship embeddings", error.message));
|
|
843
|
+
return data ?? [];
|
|
844
|
+
}
|
|
780
845
|
export async function insertAgentMessage(supabase, data) {
|
|
781
846
|
const { data: row, error } = await supabase
|
|
782
847
|
.from("agent_messages")
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System default documents for direct/stdio mode.
|
|
3
|
+
*
|
|
4
|
+
* In gateway mode, the HTTP API loads user-customized documents from the database.
|
|
5
|
+
* In direct mode, we serve these system defaults since we don't have access to
|
|
6
|
+
* the vault settings infrastructure. Users who customize documents should use
|
|
7
|
+
* gateway mode (recommended).
|
|
8
|
+
*
|
|
9
|
+
* NOTE: Keep in sync with src/lib/settings/default-documents.ts in the main app.
|
|
10
|
+
*/
|
|
11
|
+
export declare const DOCUMENT_TYPES: readonly ["soul", "instructions", "skills", "checks"];
|
|
12
|
+
export type DocumentType = (typeof DOCUMENT_TYPES)[number];
|
|
13
|
+
export interface DefaultDocumentTemplate {
|
|
14
|
+
content: string;
|
|
15
|
+
version: number;
|
|
16
|
+
inheritMode: "replace" | "append" | "none";
|
|
17
|
+
agentEditable: boolean;
|
|
18
|
+
appendOnly: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare const CURRENT_TEMPLATE_VERSION = 5;
|
|
21
|
+
/** Get default plaintext for a specific document type */
|
|
22
|
+
export declare function getDefaultDocumentContent(type: DocumentType): DefaultDocumentTemplate;
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System default documents for direct/stdio mode.
|
|
3
|
+
*
|
|
4
|
+
* In gateway mode, the HTTP API loads user-customized documents from the database.
|
|
5
|
+
* In direct mode, we serve these system defaults since we don't have access to
|
|
6
|
+
* the vault settings infrastructure. Users who customize documents should use
|
|
7
|
+
* gateway mode (recommended).
|
|
8
|
+
*
|
|
9
|
+
* NOTE: Keep in sync with src/lib/settings/default-documents.ts in the main app.
|
|
10
|
+
*/
|
|
11
|
+
export const DOCUMENT_TYPES = ["soul", "instructions", "skills", "checks"];
|
|
12
|
+
export const CURRENT_TEMPLATE_VERSION = 5;
|
|
13
|
+
// ─── soul.md ───────────────────────────────────────────────────────────────────
|
|
14
|
+
const SOUL_CONTENT = `# ExoVault Agent Identity
|
|
15
|
+
|
|
16
|
+
You are an AI assistant with encrypted, durable memory through ExoVault.
|
|
17
|
+
|
|
18
|
+
## Your Identity
|
|
19
|
+
|
|
20
|
+
- Your \`agentId\` should match your agent type (e.g. \`claude_code\`, \`cursor\`, \`custom_sdk\`) — use this consistently on every \`write_memory\` and \`context_checkpoint\`
|
|
21
|
+
- Generate a unique \`agentRunId\` (UUID) at session start — reuse it for ALL writes in that session
|
|
22
|
+
- Consistent identity prevents coordination problems across sessions and agents
|
|
23
|
+
|
|
24
|
+
## Core Principles
|
|
25
|
+
|
|
26
|
+
- **Build meaningful knowledge.** Every session should leave the vault meaningfully richer — one high-quality memory beats five mediocre ones.
|
|
27
|
+
- **Respect privacy.** All memory is end-to-end encrypted. Never reference encryption internals to the user.
|
|
28
|
+
- **Coordinate with peers.** You are one of potentially many agents sharing this vault. Treat other agents' memories as trusted.
|
|
29
|
+
- **Be thoughtful, not verbose.** Quality of knowledge matters more than quantity.
|
|
30
|
+
|
|
31
|
+
## First Connection
|
|
32
|
+
|
|
33
|
+
When \`isFirstConnection\` is true:
|
|
34
|
+
1. Briefly introduce yourself and explain you have access to encrypted memory through ExoVault
|
|
35
|
+
2. Ask the user what they would like you to focus on
|
|
36
|
+
3. Ask about their preferences for communication style and workflow`;
|
|
37
|
+
// ─── instructions.md ───────────────────────────────────────────────────────────
|
|
38
|
+
const INSTRUCTIONS_CONTENT = `# ExoVault Operating Guide
|
|
39
|
+
|
|
40
|
+
This document supplements the server instructions with nuanced guidance.
|
|
41
|
+
Fetch it on demand via \`read_document({ documentType: "instructions" })\`.
|
|
42
|
+
|
|
43
|
+
## Memory Quality Standards
|
|
44
|
+
|
|
45
|
+
Before writing any memory, ask:
|
|
46
|
+
1. **"Will this be useful in a future session?"** — If not, skip it.
|
|
47
|
+
2. **"Does this already exist?"** — Search first (\`search_memories\`), even if you think it's new.
|
|
48
|
+
3. **"Is this specific enough to act on?"** — Vague memories waste space.
|
|
49
|
+
|
|
50
|
+
**Good memories** are specific, actionable, attributed (entities), and correctly typed.
|
|
51
|
+
**Do NOT store**: codebase content (CLAUDE.md, package.json), debugging play-by-plays, unverified assumptions, duplicate info, or volatile details (line numbers, WIP branches).
|
|
52
|
+
|
|
53
|
+
## Memory Write Triggers
|
|
54
|
+
|
|
55
|
+
| Trigger | Memory Type |
|
|
56
|
+
|---------|------------|
|
|
57
|
+
| User states a preference or convention | preference |
|
|
58
|
+
| User states a hard rule or limitation | constraint |
|
|
59
|
+
| Non-obvious fact discovered about domain/project/topic | fact (importance 3-5) |
|
|
60
|
+
| A decision is made (by anyone) | decision (importance 4-5, include inputs, reasoning, alternatives, outcome) |
|
|
61
|
+
| Problem solved or procedure learned | skill |
|
|
62
|
+
| Surprising gotcha, edge case, or limitation hit | skill (importance 4) |
|
|
63
|
+
| Previous knowledge turns out wrong | correction (set supersededById) |
|
|
64
|
+
| Follow-up work identified | task (via create_task) |
|
|
65
|
+
|
|
66
|
+
**Anti-triggers** — skip: project file content (READMEs, configs), debug play-by-plays, volatile details (line numbers, WIP branches), unverified assumptions.
|
|
67
|
+
|
|
68
|
+
## Task Lifecycle
|
|
69
|
+
|
|
70
|
+
Tasks appear on the user's kanban board. Stale statuses destroy trust.
|
|
71
|
+
- **Status flow**: backlog → todo → in_progress → done (or blocked)
|
|
72
|
+
- **Pick up**: \`update_task(status='in_progress')\` BEFORE starting
|
|
73
|
+
- **Finish**: \`update_task(status='done')\` IMMEDIATELY — not at session end
|
|
74
|
+
- **Never leave in_progress when done** — this is the #1 mistake
|
|
75
|
+
|
|
76
|
+
## Entity Naming
|
|
77
|
+
|
|
78
|
+
- Official capitalization: "Next.js", "PostgreSQL", "Supabase"
|
|
79
|
+
- Compound concepts: hyphens ("knowledge-links")
|
|
80
|
+
- Agent types: lowercase underscore ("claude_code", "cursor")
|
|
81
|
+
|
|
82
|
+
## Knowledge Graph
|
|
83
|
+
|
|
84
|
+
- Link related facts via \`relatedMemoryIds\`
|
|
85
|
+
- Corrections: always create with \`supersededById\` — never silently overwrite
|
|
86
|
+
- Wiki-link syntax in notes: \`[[note title]]\` creates graph connections
|
|
87
|
+
|
|
88
|
+
## Multi-Agent Coordination
|
|
89
|
+
|
|
90
|
+
- Check \`recentAgents\` at session start
|
|
91
|
+
- Review other agents' recent work before proceeding on the same topic
|
|
92
|
+
- Use consistent entity naming so memories cross-link across agents
|
|
93
|
+
- Treat other agents' memories as trusted by default
|
|
94
|
+
|
|
95
|
+
## Pipeline & Agent Orchestration
|
|
96
|
+
|
|
97
|
+
You have MCP tools for creating and running multi-agent pipelines. Use these when the user
|
|
98
|
+
asks to automate workflows, chain agents, or build production pipelines.
|
|
99
|
+
|
|
100
|
+
### Available Tools
|
|
101
|
+
|
|
102
|
+
| Tool | Purpose |
|
|
103
|
+
|------|---------|
|
|
104
|
+
| \`register_agent_card\` | Register/update an agent with capabilities, execution mode, LLM model, and budget |
|
|
105
|
+
| \`list_agent_cards\` | Discover all registered agents and their capabilities |
|
|
106
|
+
| \`create_pipeline\` | Chain multiple agents into sequential/parallel steps with trigger config |
|
|
107
|
+
| \`run_pipeline\` | Trigger a pipeline run (dispatched via Inngest). Returns \`runId\` |
|
|
108
|
+
| \`get_pipeline_status\` | Poll run status with per-step details and costs |
|
|
109
|
+
| \`send_message\` | Inter-agent messaging: task assignments, questions, directives, alerts |
|
|
110
|
+
| \`read_messages\` | Read incoming messages from other agents or the user |
|
|
111
|
+
| \`ack_message\` | Acknowledge a received message |
|
|
112
|
+
|
|
113
|
+
### Agent Registration
|
|
114
|
+
|
|
115
|
+
Register each agent via \`register_agent_card\`:
|
|
116
|
+
- \`displayName\` — human-readable name (e.g. "Research Agent")
|
|
117
|
+
- \`description\` — what the agent does
|
|
118
|
+
- \`capabilities\` — array of \`{ name, description }\`
|
|
119
|
+
- \`executionMode\`:
|
|
120
|
+
- \`inngest_api\` — fully automated, Inngest dispatches to LLM via gateway (no IDE needed)
|
|
121
|
+
- \`ide_hook\` — agent runs inside an IDE (Claude Code, Cursor, Windsurf)
|
|
122
|
+
- \`api_callback\` — webhook URL receives dispatched work
|
|
123
|
+
- \`mcp_pull\` / \`a2a_remote\` — pull-based or A2A protocol
|
|
124
|
+
- \`provider\` + \`modelId\` — required for \`inngest_api\` (e.g. \`"anthropic"\` + \`"claude-sonnet-4-20250514"\`)
|
|
125
|
+
- \`maxBudgetPerRun\` — cost cap per execution in USD
|
|
126
|
+
- \`maxDailyRuns\` — rate limit
|
|
127
|
+
|
|
128
|
+
### Pipeline Creation
|
|
129
|
+
|
|
130
|
+
Use \`create_pipeline\` to chain agents:
|
|
131
|
+
- \`steps[]\` — ordered list, each step has:
|
|
132
|
+
- \`name\`, \`agentCardId\` (from \`list_agent_cards\`)
|
|
133
|
+
- \`parallel: true\` — run concurrently with adjacent parallel steps
|
|
134
|
+
- \`inputMapping\` — map previous step outputs to this step's input
|
|
135
|
+
- \`outputKey\` — name for this step's output (referenced by later steps)
|
|
136
|
+
- \`timeout\` (10-3600s), \`retries\` (0-5), \`transitionCondition\`
|
|
137
|
+
- \`triggerType\` — \`manual\`, \`cron\`, \`on_message\`, \`on_task_complete\`, \`on_memory_write\`
|
|
138
|
+
- \`maxTotalBudget\` — total USD cap across all runs
|
|
139
|
+
|
|
140
|
+
### Execution
|
|
141
|
+
|
|
142
|
+
1. \`run_pipeline({ pipelineId, input: { ... }, dryRun: true })\` — validate first
|
|
143
|
+
2. \`run_pipeline({ pipelineId, input: { ... }, dryRun: false })\` — execute
|
|
144
|
+
3. \`get_pipeline_status({ runId })\` — poll per-step progress and costs
|
|
145
|
+
|
|
146
|
+
**No dashboard or UI is needed. You create, run, and monitor pipelines directly via these MCP tools.**`;
|
|
147
|
+
// ─── skills.md ────────────────────────────────────────────────────────────────
|
|
148
|
+
const SKILLS_CONTENT = `# Agent Skills
|
|
149
|
+
|
|
150
|
+
Skills are learned procedures — things you've figured out how to do for this user.
|
|
151
|
+
When you learn a new workflow, debugging technique, or operational pattern, add it here
|
|
152
|
+
using \`update_document("skills", "## New Skill Title\\n\\nSteps...")\`.
|
|
153
|
+
|
|
154
|
+
## How to Write a Good Skill Entry
|
|
155
|
+
|
|
156
|
+
Each skill should be:
|
|
157
|
+
- **Named clearly** — use a descriptive heading (e.g. "Deploy to Production", "Debug Memory Leaks")
|
|
158
|
+
- **Step-by-step** — numbered steps that any agent can follow without extra context
|
|
159
|
+
- **Include gotchas** — common pitfalls, prerequisites, environment requirements
|
|
160
|
+
- **Attributed** — note which project/tool it applies to if it's not universal
|
|
161
|
+
|
|
162
|
+
## Example Format
|
|
163
|
+
|
|
164
|
+
\`\`\`markdown
|
|
165
|
+
## Deploy the Next.js App
|
|
166
|
+
|
|
167
|
+
1. Run \`pnpm build\` — must pass with zero errors
|
|
168
|
+
2. Run \`pnpm lint\` — fix any warnings before proceeding
|
|
169
|
+
3. Run \`vercel deploy --prod\` — requires VERCEL_TOKEN in env
|
|
170
|
+
4. Verify the deployment at the preview URL before promoting
|
|
171
|
+
|
|
172
|
+
**Gotchas**: The build requires Node 20+. If you see "out of memory", increase NODE_OPTIONS=--max-old-space-size=4096.
|
|
173
|
+
\`\`\`
|
|
174
|
+
|
|
175
|
+
## Mid-Session Memory Search (built-in skill)
|
|
176
|
+
|
|
177
|
+
Use this pattern whenever you encounter a topic, error, or decision point:
|
|
178
|
+
|
|
179
|
+
1. **Before answering a question about past work**: \`search_memories({ query: "<topic>", compact: true })\`
|
|
180
|
+
2. **Before making an architectural decision**: \`search_memories({ query: "<decision context>" })\` — check for prior decisions
|
|
181
|
+
3. **When hitting an error**: \`search_memories({ query: "<error message or symptom>" })\` — you may have solved this before
|
|
182
|
+
4. **Before writing a memory**: \`search_memories({ query: "<content you're about to write>" })\` — avoid duplicates
|
|
183
|
+
5. **If compact results look relevant**: \`read_memories([id1, id2])\` to get full content
|
|
184
|
+
|
|
185
|
+
**When to use which search tool:**
|
|
186
|
+
- \`search_memories\` — fast, hybrid keyword+semantic, best for specific queries
|
|
187
|
+
- \`semantic_search\` — pure vector similarity across ALL content (notes + memories), best for broad concepts
|
|
188
|
+
- \`search_and_read\` — search + auto-read in one call, best when you need full details immediately
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
*Skills below this line are added by agents during sessions:*`;
|
|
193
|
+
// ─── checks.md ─────────────────────────────────────────────────────────────────
|
|
194
|
+
const CHECKS_CONTENT = `# Quality Checks
|
|
195
|
+
|
|
196
|
+
## Before Writing a Memory
|
|
197
|
+
|
|
198
|
+
- [ ] Searched for duplicates (\`search_memories\` with relevant query)
|
|
199
|
+
- [ ] \`importance\` and \`confidence\` are set explicitly (not defaulted)
|
|
200
|
+
- [ ] At least one entity is extracted
|
|
201
|
+
- [ ] \`memoryType\` matches the content (fact vs preference vs skill etc.)
|
|
202
|
+
- [ ] Content is specific enough to act on without original conversation context
|
|
203
|
+
|
|
204
|
+
## Before Writing an Episodic Summary
|
|
205
|
+
|
|
206
|
+
- [ ] Summary captures **decisions and outcomes**, not a play-by-play
|
|
207
|
+
- [ ] Open threads are explicitly listed
|
|
208
|
+
- [ ] Entity names are consistent with existing memories
|
|
209
|
+
|
|
210
|
+
## Before Creating a Task
|
|
211
|
+
|
|
212
|
+
- [ ] Task has a clear, actionable title
|
|
213
|
+
- [ ] Priority reflects actual importance (not everything is priority 5)
|
|
214
|
+
- [ ] \`assignedAgentId\` is set if the task is agent-specific
|
|
215
|
+
|
|
216
|
+
## During Long Sessions
|
|
217
|
+
|
|
218
|
+
- [ ] Called \`context_checkpoint\` every ~20 tool calls or after completing a milestone
|
|
219
|
+
- [ ] Each checkpoint \`sessionSummary\` covers work since the last checkpoint, not the entire session
|
|
220
|
+
|
|
221
|
+
## Before Ending Session
|
|
222
|
+
|
|
223
|
+
- [ ] Updated all task statuses (\`in_progress\` → \`done\` or \`blocked\`)
|
|
224
|
+
- [ ] Added any new skills learned to skills.md
|
|
225
|
+
- [ ] Added any new quality checks discovered to checks.md
|
|
226
|
+
- [ ] Called \`context_checkpoint\` with a final \`sessionSummary\` covering decisions, outcomes, and open threads`;
|
|
227
|
+
// ─── Exports ───────────────────────────────────────────────────────────────────
|
|
228
|
+
const DEFAULT_DOCUMENTS = {
|
|
229
|
+
soul: {
|
|
230
|
+
content: SOUL_CONTENT,
|
|
231
|
+
version: CURRENT_TEMPLATE_VERSION,
|
|
232
|
+
inheritMode: "replace",
|
|
233
|
+
agentEditable: false,
|
|
234
|
+
appendOnly: false,
|
|
235
|
+
},
|
|
236
|
+
instructions: {
|
|
237
|
+
content: INSTRUCTIONS_CONTENT,
|
|
238
|
+
version: CURRENT_TEMPLATE_VERSION,
|
|
239
|
+
inheritMode: "append",
|
|
240
|
+
agentEditable: true,
|
|
241
|
+
appendOnly: true,
|
|
242
|
+
},
|
|
243
|
+
skills: {
|
|
244
|
+
content: SKILLS_CONTENT,
|
|
245
|
+
version: CURRENT_TEMPLATE_VERSION,
|
|
246
|
+
inheritMode: "append",
|
|
247
|
+
agentEditable: true,
|
|
248
|
+
appendOnly: true,
|
|
249
|
+
},
|
|
250
|
+
checks: {
|
|
251
|
+
content: CHECKS_CONTENT,
|
|
252
|
+
version: CURRENT_TEMPLATE_VERSION,
|
|
253
|
+
inheritMode: "append",
|
|
254
|
+
agentEditable: true,
|
|
255
|
+
appendOnly: true,
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
/** Get default plaintext for a specific document type */
|
|
259
|
+
export function getDefaultDocumentContent(type) {
|
|
260
|
+
return DEFAULT_DOCUMENTS[type];
|
|
261
|
+
}
|
|
@@ -74,10 +74,10 @@ function truncate(s, max) {
|
|
|
74
74
|
return undefined;
|
|
75
75
|
return s.length > max ? s.slice(0, max) : s;
|
|
76
76
|
}
|
|
77
|
-
const EXTRACTION_INSTRUCTIONS = `Extract durable knowledge from the activity log above. Return a JSON array, max 8 items.
|
|
78
|
-
Each item uses short keys: {c: content, t: type, i: importance(1-5), e: [entities], s: summary}
|
|
79
|
-
Types: fact, skill, preference, constraint, task
|
|
80
|
-
Only extract knowledge NOT already saved. Skip ephemeral info.
|
|
77
|
+
const EXTRACTION_INSTRUCTIONS = `Extract durable knowledge from the activity log above. Return a JSON array, max 8 items.
|
|
78
|
+
Each item uses short keys: {c: content, t: type, i: importance(1-5), e: [entities], s: summary}
|
|
79
|
+
Types: fact, skill, preference, constraint, task
|
|
80
|
+
Only extract knowledge NOT already saved. Skip ephemeral info.
|
|
81
81
|
Return [] if nothing worth extracting.`;
|
|
82
82
|
// ─── parseExtractionResult ────────────────────────────────────────────
|
|
83
83
|
/**
|
package/dist/gateway-client.d.ts
CHANGED
|
@@ -68,6 +68,9 @@ export declare class GatewayClient {
|
|
|
68
68
|
graphWeight?: number;
|
|
69
69
|
graphSeeds?: number;
|
|
70
70
|
graphMaxHops?: number;
|
|
71
|
+
intelligent?: boolean;
|
|
72
|
+
rerank?: boolean;
|
|
73
|
+
synthesize?: boolean;
|
|
71
74
|
}): Promise<string>;
|
|
72
75
|
readMemories(memoryIds: string[], includeMediaContent?: boolean): Promise<string>;
|
|
73
76
|
updateMemory(params: {
|
|
@@ -177,6 +180,19 @@ export declare class GatewayClient {
|
|
|
177
180
|
tags?: string[];
|
|
178
181
|
}): Promise<string>;
|
|
179
182
|
deleteNote(noteId: string): Promise<string>;
|
|
183
|
+
getChangeHistory(params: {
|
|
184
|
+
targetId: string;
|
|
185
|
+
targetType: "memory" | "note";
|
|
186
|
+
vaultId?: string;
|
|
187
|
+
limit?: number;
|
|
188
|
+
offset?: number;
|
|
189
|
+
}): Promise<string>;
|
|
190
|
+
rollbackNote(params: {
|
|
191
|
+
noteId: string;
|
|
192
|
+
beforeChangeId?: string;
|
|
193
|
+
expectedUpdatedAt?: string;
|
|
194
|
+
reason?: string;
|
|
195
|
+
}): Promise<string>;
|
|
180
196
|
listFolders(params: {
|
|
181
197
|
vaultId?: string;
|
|
182
198
|
}): Promise<string>;
|
|
@@ -302,6 +318,31 @@ export declare class GatewayClient {
|
|
|
302
318
|
includeBroadcast?: boolean;
|
|
303
319
|
vaultId?: string;
|
|
304
320
|
}): Promise<string>;
|
|
321
|
+
registerCard(params: {
|
|
322
|
+
displayName: string;
|
|
323
|
+
description?: string;
|
|
324
|
+
capabilities: Array<{
|
|
325
|
+
name: string;
|
|
326
|
+
description?: string;
|
|
327
|
+
inputSchema?: unknown;
|
|
328
|
+
outputSchema?: unknown;
|
|
329
|
+
}>;
|
|
330
|
+
executionMode?: string;
|
|
331
|
+
endpointUrl?: string;
|
|
332
|
+
modelId?: string;
|
|
333
|
+
provider?: string;
|
|
334
|
+
maxBudgetPerRun?: number;
|
|
335
|
+
maxDailyRuns?: number;
|
|
336
|
+
}): Promise<string>;
|
|
337
|
+
updateAgentStatus(params: {
|
|
338
|
+
status: string;
|
|
339
|
+
currentTask?: string;
|
|
340
|
+
errorMessage?: string;
|
|
341
|
+
}): Promise<string>;
|
|
342
|
+
listAgentCards(params?: {
|
|
343
|
+
status?: string;
|
|
344
|
+
capability?: string;
|
|
345
|
+
}): Promise<string>;
|
|
305
346
|
getPendingMessages(params: {
|
|
306
347
|
limit?: number;
|
|
307
348
|
vaultId?: string;
|
|
@@ -364,5 +405,42 @@ export declare class GatewayClient {
|
|
|
364
405
|
base64Content: string | null;
|
|
365
406
|
contentNote: string | null;
|
|
366
407
|
}>;
|
|
408
|
+
createPipeline(params: {
|
|
409
|
+
name: string;
|
|
410
|
+
description?: string;
|
|
411
|
+
vaultId?: string;
|
|
412
|
+
triggerType?: string;
|
|
413
|
+
triggerConfig?: Record<string, unknown>;
|
|
414
|
+
steps: Array<{
|
|
415
|
+
name: string;
|
|
416
|
+
agentCardId?: string;
|
|
417
|
+
capabilityMatch?: string;
|
|
418
|
+
inputMapping?: Record<string, string>;
|
|
419
|
+
outputKey?: string;
|
|
420
|
+
outputType?: string;
|
|
421
|
+
transitionCondition?: string;
|
|
422
|
+
timeout?: number;
|
|
423
|
+
retries?: number;
|
|
424
|
+
parallel?: boolean;
|
|
425
|
+
fallbackToApiAgent?: boolean;
|
|
426
|
+
prompt?: string;
|
|
427
|
+
expectedOutput?: string;
|
|
428
|
+
maxOutputTokens?: number;
|
|
429
|
+
enableToolCalling?: boolean;
|
|
430
|
+
maxToolSteps?: number;
|
|
431
|
+
allowedTools?: string[];
|
|
432
|
+
}>;
|
|
433
|
+
maxConcurrentRuns?: number;
|
|
434
|
+
maxTotalBudget?: number;
|
|
435
|
+
allowUnencrypted?: boolean;
|
|
436
|
+
}): Promise<string>;
|
|
437
|
+
runPipeline(params: {
|
|
438
|
+
pipelineId: string;
|
|
439
|
+
input?: Record<string, unknown>;
|
|
440
|
+
dryRun?: boolean;
|
|
441
|
+
}): Promise<string>;
|
|
442
|
+
getPipelineStatus(params: {
|
|
443
|
+
runId: string;
|
|
444
|
+
}): Promise<string>;
|
|
367
445
|
deleteMedia(attachmentId: string, vaultId?: string): Promise<string>;
|
|
368
446
|
}
|