@yamo/memory-mesh 3.2.5 → 3.2.6

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.
@@ -225,6 +225,12 @@ export declare class MemoryMesh {
225
225
  reliability: any;
226
226
  use_count: any;
227
227
  }>;
228
+ /**
229
+ * Get a single synthesized skill by ID
230
+ * @param {string} id - Skill ID
231
+ * @returns {Promise<Object|null>} Skill data or null if not found
232
+ */
233
+ getSkill(id: any): Promise<any>;
228
234
  /**
229
235
  * Prune skills
230
236
  */
@@ -616,12 +616,30 @@ export class MemoryMesh {
616
616
  await this.init();
617
617
  const topic = options.topic || "general_improvement";
618
618
  const enrichedPrompt = options.enrichedPrompt || topic; // PHASE 4: Use enriched prompt
619
+ const mode = options.mode || "create";
620
+ const targetSkillId = options.targetSkillId;
619
621
  // const lookback = options.lookback || 20;
620
- logger.info({ topic, enrichedPrompt }, "Synthesizing logic");
622
+ logger.info({ topic, mode, targetSkillId }, "Synthesizing logic");
621
623
  // OPTIMIZATION: If we have an execution engine (kernel), use SkillCreator!
622
624
  if (this._kernel_execute) {
623
625
  logger.info("Dispatching to SkillCreator agent...");
624
626
  try {
627
+ // Fetch target skill content if refactoring
628
+ let targetContent = "";
629
+ let targetPath = "";
630
+ if (mode === "refactor" && targetSkillId) {
631
+ const skill = await this.getSkill(targetSkillId);
632
+ if (skill) {
633
+ targetPath = skill.metadata?.source_file || "";
634
+ if (targetPath && fs.existsSync(targetPath)) {
635
+ targetContent = fs.readFileSync(targetPath, "utf8");
636
+ }
637
+ else {
638
+ // Fallback to DB content if file missing
639
+ targetContent = skill.yamo_text || "";
640
+ }
641
+ }
642
+ }
625
643
  // Use stored skill directories
626
644
  const skillDirs = this.skillDirectories;
627
645
  // Track existing .yamo files before SkillCreator runs
@@ -652,7 +670,11 @@ export class MemoryMesh {
652
670
  }
653
671
  }
654
672
  // PHASE 4: Use enriched prompt for SkillCreator
655
- await this._kernel_execute(`SkillCreator: design a new skill to handle ${enrichedPrompt}`, {
673
+ let prompt = `SkillCreator: design a new skill to handle ${enrichedPrompt}`;
674
+ if (mode === "refactor" && targetContent) {
675
+ prompt = `SkillCreator: REFACTOR and FIX the following skill. It failed with the following context: ${enrichedPrompt}.\n\nEXISTING SKILL CONTENT:\n${targetContent}`;
676
+ }
677
+ await this._kernel_execute(prompt, {
656
678
  v1_1_enabled: true,
657
679
  });
658
680
  // Find newly created .yamo file
@@ -813,6 +835,37 @@ description: Auto-generated skill to handle: ${enrichedPrompt || topic}
813
835
  throw new Error(`Failed to update skill reliability: ${error.message}`);
814
836
  }
815
837
  }
838
+ /**
839
+ * Get a single synthesized skill by ID
840
+ * @param {string} id - Skill ID
841
+ * @returns {Promise<Object|null>} Skill data or null if not found
842
+ */
843
+ async getSkill(id) {
844
+ await this.init();
845
+ if (!this.skillTable) {
846
+ return null;
847
+ }
848
+ try {
849
+ const results = await this.skillTable
850
+ .query()
851
+ .filter(`id == '${id}'`)
852
+ .toArray();
853
+ if (results.length === 0) {
854
+ return null;
855
+ }
856
+ const record = results[0];
857
+ return {
858
+ ...record,
859
+ metadata: typeof record.metadata === "string"
860
+ ? JSON.parse(record.metadata)
861
+ : record.metadata,
862
+ };
863
+ }
864
+ catch (error) {
865
+ logger.warn({ err: error, id }, "Failed to get skill");
866
+ return null;
867
+ }
868
+ }
816
869
  /**
817
870
  * Prune skills
818
871
  */
@@ -1366,7 +1419,7 @@ description: Auto-generated skill to handle: ${enrichedPrompt || topic}
1366
1419
  `agent: MemoryMesh_${this.agentId};`,
1367
1420
  `intent: distill_wisdom_from_execution;`,
1368
1421
  `context:`,
1369
- ` original_context;${situation.replace(/;/g, ",")};`,
1422
+ ` original_context;${situation.replace(/;/g, "%3B")};`,
1370
1423
  ` error_pattern;${patternId};`,
1371
1424
  ` severity;${severity};`,
1372
1425
  ` timestamp;${timestamp};`,
@@ -1376,13 +1429,13 @@ description: Auto-generated skill to handle: ${enrichedPrompt || topic}
1376
1429
  `priority: high;`,
1377
1430
  `output:`,
1378
1431
  ` lesson_id;${lessonId};`,
1379
- ` oversight_description;${oversight.replace(/;/g, ",")};`,
1380
- ` preventative_rule;${preventativeRule.replace(/;/g, ",")};`,
1432
+ ` oversight_description;${oversight.replace(/;/g, "%3B")};`,
1433
+ ` preventative_rule;${preventativeRule.replace(/;/g, "%3B")};`,
1381
1434
  ` rule_confidence;${confidence};`,
1382
1435
  `meta:`,
1383
- ` rationale;${fix.replace(/;/g, ",")};`,
1384
- ` applicability_scope;${applicableScope.replace(/;/g, ",")};`,
1385
- ` inverse_lesson;${inverseLesson.replace(/;/g, ",")};`,
1436
+ ` rationale;${fix.replace(/;/g, "%3B")};`,
1437
+ ` applicability_scope;${applicableScope.replace(/;/g, "%3B")};`,
1438
+ ` inverse_lesson;${inverseLesson.replace(/;/g, "%3B")};`,
1386
1439
  ` confidence;${confidence};`,
1387
1440
  `log: lesson_learned;timestamp;${timestamp};pattern;${patternId};severity;${severity};id;${lessonId};`,
1388
1441
  `handoff: SubconsciousReflector;`,
@@ -648,12 +648,32 @@ export class MemoryMesh {
648
648
  await this.init();
649
649
  const topic = options.topic || "general_improvement";
650
650
  const enrichedPrompt = options.enrichedPrompt || topic; // PHASE 4: Use enriched prompt
651
+ const mode = options.mode || "create";
652
+ const targetSkillId = options.targetSkillId;
653
+
651
654
  // const lookback = options.lookback || 20;
652
- logger.info({ topic, enrichedPrompt }, "Synthesizing logic");
655
+ logger.info({ topic, mode, targetSkillId }, "Synthesizing logic");
656
+
653
657
  // OPTIMIZATION: If we have an execution engine (kernel), use SkillCreator!
654
658
  if (this._kernel_execute) {
655
659
  logger.info("Dispatching to SkillCreator agent...");
656
660
  try {
661
+ // Fetch target skill content if refactoring
662
+ let targetContent = "";
663
+ let targetPath = "";
664
+ if (mode === "refactor" && targetSkillId) {
665
+ const skill = await this.getSkill(targetSkillId);
666
+ if (skill) {
667
+ targetPath = skill.metadata?.source_file || "";
668
+ if (targetPath && fs.existsSync(targetPath)) {
669
+ targetContent = fs.readFileSync(targetPath, "utf8");
670
+ } else {
671
+ // Fallback to DB content if file missing
672
+ targetContent = skill.yamo_text || "";
673
+ }
674
+ }
675
+ }
676
+
657
677
  // Use stored skill directories
658
678
  const skillDirs = this.skillDirectories;
659
679
  // Track existing .yamo files before SkillCreator runs
@@ -683,8 +703,14 @@ export class MemoryMesh {
683
703
  walk(dir);
684
704
  }
685
705
  }
706
+
686
707
  // PHASE 4: Use enriched prompt for SkillCreator
687
- await this._kernel_execute(`SkillCreator: design a new skill to handle ${enrichedPrompt}`, {
708
+ let prompt = `SkillCreator: design a new skill to handle ${enrichedPrompt}`;
709
+ if (mode === "refactor" && targetContent) {
710
+ prompt = `SkillCreator: REFACTOR and FIX the following skill. It failed with the following context: ${enrichedPrompt}.\n\nEXISTING SKILL CONTENT:\n${targetContent}`;
711
+ }
712
+
713
+ await this._kernel_execute(prompt, {
688
714
  v1_1_enabled: true,
689
715
  });
690
716
  // Find newly created .yamo file
@@ -845,6 +871,37 @@ description: Auto-generated skill to handle: ${enrichedPrompt || topic}
845
871
  throw new Error(`Failed to update skill reliability: ${error.message}`);
846
872
  }
847
873
  }
874
+ /**
875
+ * Get a single synthesized skill by ID
876
+ * @param {string} id - Skill ID
877
+ * @returns {Promise<Object|null>} Skill data or null if not found
878
+ */
879
+ async getSkill(id) {
880
+ await this.init();
881
+ if (!this.skillTable) {
882
+ return null;
883
+ }
884
+ try {
885
+ const results = await this.skillTable
886
+ .query()
887
+ .filter(`id == '${id}'`)
888
+ .toArray();
889
+ if (results.length === 0) {
890
+ return null;
891
+ }
892
+ const record = results[0];
893
+ return {
894
+ ...record,
895
+ metadata: typeof record.metadata === "string"
896
+ ? JSON.parse(record.metadata)
897
+ : record.metadata,
898
+ };
899
+ }
900
+ catch (error) {
901
+ logger.warn({ err: error, id }, "Failed to get skill");
902
+ return null;
903
+ }
904
+ }
848
905
  /**
849
906
  * Prune skills
850
907
  */
@@ -1418,7 +1475,7 @@ description: Auto-generated skill to handle: ${enrichedPrompt || topic}
1418
1475
  `agent: MemoryMesh_${this.agentId};`,
1419
1476
  `intent: distill_wisdom_from_execution;`,
1420
1477
  `context:`,
1421
- ` original_context;${situation.replace(/;/g, ",")};`,
1478
+ ` original_context;${situation.replace(/;/g, "%3B")};`,
1422
1479
  ` error_pattern;${patternId};`,
1423
1480
  ` severity;${severity};`,
1424
1481
  ` timestamp;${timestamp};`,
@@ -1428,13 +1485,13 @@ description: Auto-generated skill to handle: ${enrichedPrompt || topic}
1428
1485
  `priority: high;`,
1429
1486
  `output:`,
1430
1487
  ` lesson_id;${lessonId};`,
1431
- ` oversight_description;${oversight.replace(/;/g, ",")};`,
1432
- ` preventative_rule;${preventativeRule.replace(/;/g, ",")};`,
1488
+ ` oversight_description;${oversight.replace(/;/g, "%3B")};`,
1489
+ ` preventative_rule;${preventativeRule.replace(/;/g, "%3B")};`,
1433
1490
  ` rule_confidence;${confidence};`,
1434
1491
  `meta:`,
1435
- ` rationale;${fix.replace(/;/g, ",")};`,
1436
- ` applicability_scope;${applicableScope.replace(/;/g, ",")};`,
1437
- ` inverse_lesson;${inverseLesson.replace(/;/g, ",")};`,
1492
+ ` rationale;${fix.replace(/;/g, "%3B")};`,
1493
+ ` applicability_scope;${applicableScope.replace(/;/g, "%3B")};`,
1494
+ ` inverse_lesson;${inverseLesson.replace(/;/g, "%3B")};`,
1438
1495
  ` confidence;${confidence};`,
1439
1496
  `log: lesson_learned;timestamp;${timestamp};pattern;${patternId};severity;${severity};id;${lessonId};`,
1440
1497
  `handoff: SubconsciousReflector;`,
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Prompt Security Utilities — RFC-0010-A
3
+ *
4
+ * Shared sanitisation functions for all LLM prompt construction.
5
+ *
6
+ * Rule (RFC-0010-A §5): Any string originating from user input, memory retrieval,
7
+ * or execution logs that is interpolated into an LLM prompt MUST pass through
8
+ * sanitizePromptField() before interpolation.
9
+ *
10
+ * Audit sweep (2026-03-14):
11
+ * - lib/memory/memory-mesh.ts — HyDE query expansion uses `scrubbed` (pre-sanitised)
12
+ * - lib/memory/memory-mesh.ts — synthesis prompt uses `scrubbed` (pre-sanitised)
13
+ * - lib/llm/client.ts — reflection prompt: static system + formatted memory list
14
+ * - lib/memory/memory-translator.ts — role-confusion protection already applied
15
+ */
16
+ /**
17
+ * Sanitise a free-text value before injecting it into an LLM prompt.
18
+ *
19
+ * Actions:
20
+ * - Collapses \r\n and \n to a single space (prevents instruction-injection via newlines)
21
+ * - Escapes double-quotes → \" (prevents breaking out of JSON-encoded fields)
22
+ * - Caps at maxLen characters (prevents token-budget exhaustion)
23
+ */
24
+ export declare function sanitizePromptField(value: string, maxLen?: number): string;
25
+ /**
26
+ * Sanitise a memory or agent identifier before injecting it into an LLM prompt.
27
+ *
28
+ * Allows only alphanumeric characters, underscores, and hyphens.
29
+ * Strips everything else to prevent SQL-style keywords, path traversal, or quote
30
+ * characters from appearing in the prompt even if the ID was tampered with in storage.
31
+ */
32
+ export declare function sanitizeSkillId(value: string, maxLen?: number): string;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Prompt Security Utilities — RFC-0010-A
3
+ *
4
+ * Shared sanitisation functions for all LLM prompt construction.
5
+ *
6
+ * Rule (RFC-0010-A §5): Any string originating from user input, memory retrieval,
7
+ * or execution logs that is interpolated into an LLM prompt MUST pass through
8
+ * sanitizePromptField() before interpolation.
9
+ *
10
+ * Audit sweep (2026-03-14):
11
+ * - lib/memory/memory-mesh.ts — HyDE query expansion uses `scrubbed` (pre-sanitised)
12
+ * - lib/memory/memory-mesh.ts — synthesis prompt uses `scrubbed` (pre-sanitised)
13
+ * - lib/llm/client.ts — reflection prompt: static system + formatted memory list
14
+ * - lib/memory/memory-translator.ts — role-confusion protection already applied
15
+ */
16
+ /**
17
+ * Sanitise a free-text value before injecting it into an LLM prompt.
18
+ *
19
+ * Actions:
20
+ * - Collapses \r\n and \n to a single space (prevents instruction-injection via newlines)
21
+ * - Escapes double-quotes → \" (prevents breaking out of JSON-encoded fields)
22
+ * - Caps at maxLen characters (prevents token-budget exhaustion)
23
+ */
24
+ export function sanitizePromptField(value, maxLen = 256) {
25
+ if (!value)
26
+ return "";
27
+ return value
28
+ .replace(/[\r\n]+/g, " ")
29
+ .replace(/"/g, '\\"')
30
+ .slice(0, maxLen);
31
+ }
32
+ /**
33
+ * Sanitise a memory or agent identifier before injecting it into an LLM prompt.
34
+ *
35
+ * Allows only alphanumeric characters, underscores, and hyphens.
36
+ * Strips everything else to prevent SQL-style keywords, path traversal, or quote
37
+ * characters from appearing in the prompt even if the ID was tampered with in storage.
38
+ */
39
+ export function sanitizeSkillId(value, maxLen = 64) {
40
+ if (!value)
41
+ return "";
42
+ return value.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, maxLen);
43
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Prompt Security Utilities — RFC-0010-A
3
+ *
4
+ * Shared sanitisation functions for all LLM prompt construction.
5
+ *
6
+ * Rule (RFC-0010-A §5): Any string originating from user input, memory retrieval,
7
+ * or execution logs that is interpolated into an LLM prompt MUST pass through
8
+ * sanitizePromptField() before interpolation.
9
+ *
10
+ * Audit sweep (2026-03-14):
11
+ * - lib/memory/memory-mesh.ts — HyDE query expansion uses `scrubbed` (pre-sanitised)
12
+ * - lib/memory/memory-mesh.ts — synthesis prompt uses `scrubbed` (pre-sanitised)
13
+ * - lib/llm/client.ts — reflection prompt: static system + formatted memory list
14
+ * - lib/memory/memory-translator.ts — role-confusion protection already applied
15
+ */
16
+
17
+ /**
18
+ * Sanitise a free-text value before injecting it into an LLM prompt.
19
+ *
20
+ * Actions:
21
+ * - Collapses \r\n and \n to a single space (prevents instruction-injection via newlines)
22
+ * - Escapes double-quotes → \" (prevents breaking out of JSON-encoded fields)
23
+ * - Caps at maxLen characters (prevents token-budget exhaustion)
24
+ */
25
+ export function sanitizePromptField(value: string, maxLen = 256): string {
26
+ if (!value) return "";
27
+ return value
28
+ .replace(/[\r\n]+/g, " ")
29
+ .replace(/"/g, '\\"')
30
+ .slice(0, maxLen);
31
+ }
32
+
33
+ /**
34
+ * Sanitise a memory or agent identifier before injecting it into an LLM prompt.
35
+ *
36
+ * Allows only alphanumeric characters, underscores, and hyphens.
37
+ * Strips everything else to prevent SQL-style keywords, path traversal, or quote
38
+ * characters from appearing in the prompt even if the ID was tampered with in storage.
39
+ */
40
+ export function sanitizeSkillId(value: string, maxLen = 64): string {
41
+ if (!value) return "";
42
+ return value.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, maxLen);
43
+ }
@@ -1,12 +1,13 @@
1
1
  /**
2
- * YAMO Emitter - Constructs structured YAMO blocks for auditability
2
+ * YAMO Emitter - Constructs structured YAMO ABNF blocks for auditability
3
3
  *
4
- * Based on YAMO Protocol specification:
5
- * - Semicolon-terminated key-value pairs
6
- * - Agent/Intent/Context/Constraints/Meta/Output structure
7
- * - Supports reflect, retain, recall operations
4
+ * Based on YAMO Protocol RFC-0011 §3.2 (ABNF colon-separated multi-line format).
5
+ * This format is DISTINCT from the flat wire format (RFC-0008 / RFC-0014).
8
6
  *
9
- * Reference: Hindsight project's yamo_integration.py
7
+ * Escaping: RFC-0014 semicolons in values are percent-encoded as `%3B`.
8
+ * (Supersedes the comma-escape scheme used prior to 2026-03-14.)
9
+ *
10
+ * Reference: yamo-os lib/yamo/emitter.ts (canonical implementation)
10
11
  */
11
12
  /**
12
13
  * YamoEmitter class for building YAMO protocol blocks
@@ -1,14 +1,19 @@
1
1
  // @ts-nocheck
2
2
  /**
3
- * YAMO Emitter - Constructs structured YAMO blocks for auditability
3
+ * YAMO Emitter - Constructs structured YAMO ABNF blocks for auditability
4
4
  *
5
- * Based on YAMO Protocol specification:
6
- * - Semicolon-terminated key-value pairs
7
- * - Agent/Intent/Context/Constraints/Meta/Output structure
8
- * - Supports reflect, retain, recall operations
5
+ * Based on YAMO Protocol RFC-0011 §3.2 (ABNF colon-separated multi-line format).
6
+ * This format is DISTINCT from the flat wire format (RFC-0008 / RFC-0014).
9
7
  *
10
- * Reference: Hindsight project's yamo_integration.py
8
+ * Escaping: RFC-0014 semicolons in values are percent-encoded as `%3B`.
9
+ * (Supersedes the comma-escape scheme used prior to 2026-03-14.)
10
+ *
11
+ * Reference: yamo-os lib/yamo/emitter.ts (canonical implementation)
11
12
  */
13
+ /** Percent-encode semicolons in a value so they don't break the YAMO line format. */
14
+ function escapeValue(value) {
15
+ return String(value).replace(/;/g, "%3B");
16
+ }
12
17
  /**
13
18
  * YamoEmitter class for building YAMO protocol blocks
14
19
  * YAMO (Yet Another Multi-agent Orchestration) blocks provide
@@ -22,17 +27,17 @@ export class YamoEmitter {
22
27
  static buildReflectBlock(params) {
23
28
  const { topic, memoryCount, agentId = "default", reflection, confidence = 0.8, } = params;
24
29
  const timestamp = new Date().toISOString();
25
- return `agent: MemoryMesh_${agentId};
30
+ return `agent: MemoryMesh_${escapeValue(agentId)};
26
31
  intent: synthesize_insights_from_context;
27
32
  context:
28
- topic;${topic || "general"};
33
+ topic;${escapeValue(topic || "general")};
29
34
  memory_count;${memoryCount};
30
35
  timestamp;${timestamp};
31
36
  constraints:
32
37
  hypothesis;Reflection generates new insights from existing facts;
33
38
  priority: high;
34
39
  output:
35
- reflection;${reflection};
40
+ reflection;${escapeValue(reflection)};
36
41
  confidence;${confidence};
37
42
  meta:
38
43
  rationale;Synthesized from ${memoryCount} relevant memories;
@@ -50,26 +55,26 @@ handoff: End;
50
55
  const { content, metadata: _metadata = {}, id, agentId = "default", memoryType = "event", } = params;
51
56
  const timestamp = new Date().toISOString();
52
57
  const contentPreview = content.length > 100 ? `${content.substring(0, 100)}...` : content;
53
- // Escape semicolons in content for YAMO format
54
- const escapedContent = contentPreview.replace(/;/g, ",");
55
- return `agent: MemoryMesh_${agentId};
58
+ // RFC-0014: percent-encode semicolons (replaces legacy comma-escape)
59
+ const escapedContent = escapeValue(contentPreview);
60
+ return `agent: MemoryMesh_${escapeValue(agentId)};
56
61
  intent: store_memory_for_future_retrieval;
57
62
  context:
58
- memory_id;${id};
59
- memory_type;${memoryType};
63
+ memory_id;${escapeValue(id)};
64
+ memory_type;${escapeValue(memoryType)};
60
65
  timestamp;${timestamp};
61
66
  content_length;${content.length};
62
67
  constraints:
63
68
  hypothesis;New information should be integrated into world model;
64
69
  priority: medium;
65
70
  output:
66
- memory_stored;${id};
71
+ memory_stored;${escapeValue(id)};
67
72
  content_preview;${escapedContent};
68
73
  meta:
69
74
  rationale;Memory persisted for semantic search and retrieval;
70
75
  observation;Content vectorized and stored in LanceDB;
71
76
  confidence;1.0;
72
- log: memory_retained;timestamp;${timestamp};id;${id};type;${memoryType};
77
+ log: memory_retained;timestamp;${timestamp};id;${escapeValue(id)};type;${escapeValue(memoryType)};
73
78
  handoff: End;
74
79
  `;
75
80
  }
@@ -81,11 +86,11 @@ handoff: End;
81
86
  const { query, resultCount, limit = 10, agentId = "default", searchType = "semantic", } = params;
82
87
  const timestamp = new Date().toISOString();
83
88
  const recallRatio = resultCount > 0 ? (resultCount / limit).toFixed(2) : "0.00";
84
- return `agent: MemoryMesh_${agentId};
89
+ return `agent: MemoryMesh_${escapeValue(agentId)};
85
90
  intent: retrieve_relevant_memories;
86
91
  context:
87
- query;${query};
88
- search_type;${searchType};
92
+ query;${escapeValue(query)};
93
+ search_type;${escapeValue(searchType)};
89
94
  requested_limit;${limit};
90
95
  timestamp;${timestamp};
91
96
  constraints:
@@ -98,7 +103,7 @@ meta:
98
103
  rationale;Semantic search finds similar content by vector similarity;
99
104
  observation;${resultCount} memories found matching query;
100
105
  confidence;${resultCount > 0 ? "0.9" : "0.5"};
101
- log: memory_recalled;timestamp;${timestamp};results;${resultCount};query;${query};
106
+ log: memory_recalled;timestamp;${timestamp};results;${resultCount};query;${escapeValue(query)};
102
107
  handoff: End;
103
108
  `;
104
109
  }
@@ -109,22 +114,22 @@ handoff: End;
109
114
  static buildDeleteBlock(params) {
110
115
  const { id, agentId = "default", reason = "user_request" } = params;
111
116
  const timestamp = new Date().toISOString();
112
- return `agent: MemoryMesh_${agentId};
117
+ return `agent: MemoryMesh_${escapeValue(agentId)};
113
118
  intent: remove_memory_from_storage;
114
119
  context:
115
- memory_id;${id};
116
- reason;${reason};
120
+ memory_id;${escapeValue(id)};
121
+ reason;${escapeValue(reason)};
117
122
  timestamp;${timestamp};
118
123
  constraints:
119
124
  hypothesis;Memory removal should be traceable for audit;
120
125
  priority: low;
121
126
  output:
122
- deleted;${id};
127
+ deleted;${escapeValue(id)};
123
128
  meta:
124
129
  rationale;Memory removed from vector store;
125
130
  observation;Deletion recorded for provenance;
126
131
  confidence;1.0;
127
- log: memory_deleted;timestamp;${timestamp};id;${id};
132
+ log: memory_deleted;timestamp;${timestamp};id;${escapeValue(id)};
128
133
  handoff: End;
129
134
  `;
130
135
  }
@@ -157,7 +162,8 @@ handoff: End;
157
162
  // Allow empty lines and comments
158
163
  if (trimmed &&
159
164
  !trimmed.startsWith("agent:") &&
160
- !trimmed.startsWith("handoff:")) {
165
+ !trimmed.startsWith("handoff:") &&
166
+ !trimmed.endsWith(":")) {
161
167
  errors.push(`Line not semicolon-terminated: ${trimmed.substring(0, 50)}`);
162
168
  }
163
169
  }
@@ -1,14 +1,20 @@
1
1
  // @ts-nocheck
2
2
  /**
3
- * YAMO Emitter - Constructs structured YAMO blocks for auditability
3
+ * YAMO Emitter - Constructs structured YAMO ABNF blocks for auditability
4
4
  *
5
- * Based on YAMO Protocol specification:
6
- * - Semicolon-terminated key-value pairs
7
- * - Agent/Intent/Context/Constraints/Meta/Output structure
8
- * - Supports reflect, retain, recall operations
5
+ * Based on YAMO Protocol RFC-0011 §3.2 (ABNF colon-separated multi-line format).
6
+ * This format is DISTINCT from the flat wire format (RFC-0008 / RFC-0014).
9
7
  *
10
- * Reference: Hindsight project's yamo_integration.py
8
+ * Escaping: RFC-0014 semicolons in values are percent-encoded as `%3B`.
9
+ * (Supersedes the comma-escape scheme used prior to 2026-03-14.)
10
+ *
11
+ * Reference: yamo-os lib/yamo/emitter.ts (canonical implementation)
11
12
  */
13
+
14
+ /** Percent-encode semicolons in a value so they don't break the YAMO line format. */
15
+ function escapeValue(value: string): string {
16
+ return String(value).replace(/;/g, "%3B");
17
+ }
12
18
  /**
13
19
  * YamoEmitter class for building YAMO protocol blocks
14
20
  * YAMO (Yet Another Multi-agent Orchestration) blocks provide
@@ -22,17 +28,17 @@ export class YamoEmitter {
22
28
  static buildReflectBlock(params) {
23
29
  const { topic, memoryCount, agentId = "default", reflection, confidence = 0.8, } = params;
24
30
  const timestamp = new Date().toISOString();
25
- return `agent: MemoryMesh_${agentId};
31
+ return `agent: MemoryMesh_${escapeValue(agentId)};
26
32
  intent: synthesize_insights_from_context;
27
33
  context:
28
- topic;${topic || "general"};
34
+ topic;${escapeValue(topic || "general")};
29
35
  memory_count;${memoryCount};
30
36
  timestamp;${timestamp};
31
37
  constraints:
32
38
  hypothesis;Reflection generates new insights from existing facts;
33
39
  priority: high;
34
40
  output:
35
- reflection;${reflection};
41
+ reflection;${escapeValue(reflection)};
36
42
  confidence;${confidence};
37
43
  meta:
38
44
  rationale;Synthesized from ${memoryCount} relevant memories;
@@ -50,26 +56,26 @@ handoff: End;
50
56
  const { content, metadata: _metadata = {}, id, agentId = "default", memoryType = "event", } = params;
51
57
  const timestamp = new Date().toISOString();
52
58
  const contentPreview = content.length > 100 ? `${content.substring(0, 100)}...` : content;
53
- // Escape semicolons in content for YAMO format
54
- const escapedContent = contentPreview.replace(/;/g, ",");
55
- return `agent: MemoryMesh_${agentId};
59
+ // RFC-0014: percent-encode semicolons (replaces legacy comma-escape)
60
+ const escapedContent = escapeValue(contentPreview);
61
+ return `agent: MemoryMesh_${escapeValue(agentId)};
56
62
  intent: store_memory_for_future_retrieval;
57
63
  context:
58
- memory_id;${id};
59
- memory_type;${memoryType};
64
+ memory_id;${escapeValue(id)};
65
+ memory_type;${escapeValue(memoryType)};
60
66
  timestamp;${timestamp};
61
67
  content_length;${content.length};
62
68
  constraints:
63
69
  hypothesis;New information should be integrated into world model;
64
70
  priority: medium;
65
71
  output:
66
- memory_stored;${id};
72
+ memory_stored;${escapeValue(id)};
67
73
  content_preview;${escapedContent};
68
74
  meta:
69
75
  rationale;Memory persisted for semantic search and retrieval;
70
76
  observation;Content vectorized and stored in LanceDB;
71
77
  confidence;1.0;
72
- log: memory_retained;timestamp;${timestamp};id;${id};type;${memoryType};
78
+ log: memory_retained;timestamp;${timestamp};id;${escapeValue(id)};type;${escapeValue(memoryType)};
73
79
  handoff: End;
74
80
  `;
75
81
  }
@@ -81,11 +87,11 @@ handoff: End;
81
87
  const { query, resultCount, limit = 10, agentId = "default", searchType = "semantic", } = params;
82
88
  const timestamp = new Date().toISOString();
83
89
  const recallRatio = resultCount > 0 ? (resultCount / limit).toFixed(2) : "0.00";
84
- return `agent: MemoryMesh_${agentId};
90
+ return `agent: MemoryMesh_${escapeValue(agentId)};
85
91
  intent: retrieve_relevant_memories;
86
92
  context:
87
- query;${query};
88
- search_type;${searchType};
93
+ query;${escapeValue(query)};
94
+ search_type;${escapeValue(searchType)};
89
95
  requested_limit;${limit};
90
96
  timestamp;${timestamp};
91
97
  constraints:
@@ -98,7 +104,7 @@ meta:
98
104
  rationale;Semantic search finds similar content by vector similarity;
99
105
  observation;${resultCount} memories found matching query;
100
106
  confidence;${resultCount > 0 ? "0.9" : "0.5"};
101
- log: memory_recalled;timestamp;${timestamp};results;${resultCount};query;${query};
107
+ log: memory_recalled;timestamp;${timestamp};results;${resultCount};query;${escapeValue(query)};
102
108
  handoff: End;
103
109
  `;
104
110
  }
@@ -109,22 +115,22 @@ handoff: End;
109
115
  static buildDeleteBlock(params) {
110
116
  const { id, agentId = "default", reason = "user_request" } = params;
111
117
  const timestamp = new Date().toISOString();
112
- return `agent: MemoryMesh_${agentId};
118
+ return `agent: MemoryMesh_${escapeValue(agentId)};
113
119
  intent: remove_memory_from_storage;
114
120
  context:
115
- memory_id;${id};
116
- reason;${reason};
121
+ memory_id;${escapeValue(id)};
122
+ reason;${escapeValue(reason)};
117
123
  timestamp;${timestamp};
118
124
  constraints:
119
125
  hypothesis;Memory removal should be traceable for audit;
120
126
  priority: low;
121
127
  output:
122
- deleted;${id};
128
+ deleted;${escapeValue(id)};
123
129
  meta:
124
130
  rationale;Memory removed from vector store;
125
131
  observation;Deletion recorded for provenance;
126
132
  confidence;1.0;
127
- log: memory_deleted;timestamp;${timestamp};id;${id};
133
+ log: memory_deleted;timestamp;${timestamp};id;${escapeValue(id)};
128
134
  handoff: End;
129
135
  `;
130
136
  }
@@ -157,7 +163,8 @@ handoff: End;
157
163
  // Allow empty lines and comments
158
164
  if (trimmed &&
159
165
  !trimmed.startsWith("agent:") &&
160
- !trimmed.startsWith("handoff:")) {
166
+ !trimmed.startsWith("handoff:") &&
167
+ !trimmed.endsWith(":")) {
161
168
  errors.push(`Line not semicolon-terminated: ${trimmed.substring(0, 50)}`);
162
169
  }
163
170
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yamo/memory-mesh",
3
- "version": "3.2.5",
3
+ "version": "3.2.6",
4
4
  "description": "Portable semantic memory system with Layer 0 Scrubber for YAMO agents (v3 Singularity Edition)",
5
5
  "repository": {
6
6
  "type": "git",