@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.
- package/lib/memory/memory-mesh.d.ts +6 -0
- package/lib/memory/memory-mesh.js +61 -8
- package/lib/memory/memory-mesh.ts +65 -8
- package/lib/utils/prompt-security.d.ts +32 -0
- package/lib/utils/prompt-security.js +43 -0
- package/lib/utils/prompt-security.ts +43 -0
- package/lib/yamo/emitter.d.ts +7 -6
- package/lib/yamo/emitter.js +32 -26
- package/lib/yamo/emitter.ts +33 -26
- package/package.json +1 -1
|
@@ -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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
+
}
|
package/lib/yamo/emitter.d.ts
CHANGED
|
@@ -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
|
|
5
|
-
*
|
|
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
|
-
*
|
|
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
|
package/lib/yamo/emitter.js
CHANGED
|
@@ -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
|
|
6
|
-
*
|
|
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
|
-
*
|
|
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
|
-
//
|
|
54
|
-
const escapedContent = contentPreview
|
|
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
|
}
|
package/lib/yamo/emitter.ts
CHANGED
|
@@ -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
|
|
6
|
-
*
|
|
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
|
-
*
|
|
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
|
-
//
|
|
54
|
-
const escapedContent = contentPreview
|
|
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
|
}
|