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.
Files changed (51) hide show
  1. package/dist/db.d.ts +47 -2
  2. package/dist/db.js +75 -10
  3. package/dist/default-documents.d.ts +22 -0
  4. package/dist/default-documents.js +261 -0
  5. package/dist/extraction-prompt.js +4 -4
  6. package/dist/gateway-client.d.ts +78 -0
  7. package/dist/gateway-client.js +102 -0
  8. package/dist/gateway-init.d.ts +1 -1
  9. package/dist/gateway-init.js +56 -6
  10. package/dist/graph-extraction.d.ts +44 -0
  11. package/dist/graph-extraction.js +211 -0
  12. package/dist/index.js +151 -1
  13. package/dist/rlm/verify.js +6 -6
  14. package/dist/rlm/writeback.js +10 -10
  15. package/dist/stopwords.d.ts +6 -0
  16. package/dist/stopwords.js +9 -0
  17. package/dist/tools/adversarial-context.d.ts +18 -0
  18. package/dist/tools/adversarial-context.js +45 -0
  19. package/dist/tools/agent-cards.d.ts +26 -0
  20. package/dist/tools/agent-cards.js +9 -0
  21. package/dist/tools/agent-pipelines.d.ts +37 -0
  22. package/dist/tools/agent-pipelines.js +9 -0
  23. package/dist/tools/contradiction-detection.d.ts +18 -0
  24. package/dist/tools/contradiction-detection.js +33 -0
  25. package/dist/tools/entity-extraction-utils.d.ts +9 -0
  26. package/dist/tools/entity-extraction-utils.js +35 -0
  27. package/dist/tools/explore-graph.d.ts +10 -0
  28. package/dist/tools/explore-graph.js +17 -0
  29. package/dist/tools/intent-detection.d.ts +8 -0
  30. package/dist/tools/intent-detection.js +50 -0
  31. package/dist/tools/knowledge-links.js +1 -0
  32. package/dist/tools/llm-call.d.ts +20 -0
  33. package/dist/tools/llm-call.js +44 -0
  34. package/dist/tools/llm-reranker.d.ts +39 -0
  35. package/dist/tools/llm-reranker.js +130 -0
  36. package/dist/tools/multi-step-retrieval.d.ts +28 -0
  37. package/dist/tools/multi-step-retrieval.js +39 -0
  38. package/dist/tools/pipeline-router.d.ts +32 -0
  39. package/dist/tools/pipeline-router.js +97 -0
  40. package/dist/tools/query-classifier.d.ts +43 -0
  41. package/dist/tools/query-classifier.js +321 -0
  42. package/dist/tools/search-memories.d.ts +4 -0
  43. package/dist/tools/search-memories.js +386 -21
  44. package/dist/tools/session-start.d.ts +1 -0
  45. package/dist/tools/session-start.js +66 -13
  46. package/dist/tools/synthesize.d.ts +27 -0
  47. package/dist/tools/synthesize.js +100 -0
  48. package/dist/tools/update-memory.js +2 -0
  49. package/dist/tools/write-memory.d.ts +10 -1
  50. package/dist/tools/write-memory.js +288 -19
  51. 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<number>;
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
- query_embedding: JSON.stringify(embedding),
292
- match_threshold: threshold,
293
- match_count: count,
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
- query_embedding: JSON.stringify(embedding),
303
- match_threshold: threshold,
304
- match_count: count,
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 0;
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?.length ?? 0;
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
  /**
@@ -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
  }