Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion platform/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export {
export type { ModelCatalogProvider, PipelineModelPreset } from "./llm-model-catalog";
export { parseManifest, generateManifest } from "./manifest";
export { parseSoul, generateSoul } from "./soul";
export { parseMemory, generateMemory } from "./memory";
export { parseMemory, generateMemory, type ParsedMemory } from "./memory";
export {
NETWORK_MODES,
normalizeNetworkMode,
Expand Down
61 changes: 58 additions & 3 deletions platform/core/src/memory.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,61 @@
export function parseMemory(markdown: string): Record<string, any> {
// TODO: Parse memory markdown into structured data
return { raw: markdown };
export interface ParsedMemory {
/** Content under the "## User Profile" section. */
userProfile: string;
/** Content under the "## Key Facts" section. */
keyFacts: string;
/** Content under the "## Ongoing Context" section. */
ongoingContext: string;
/** Content between MANUAL:START and MANUAL:END markers. */
manualNotes: string;
/** The full raw markdown input, preserved for round-tripping. */
raw: string;
}

/**
* Parse a Signet memory markdown file into structured sections.
*
* Extracts content from well-known `## ` headings and the
* `<!-- MANUAL:START -->` / `<!-- MANUAL:END -->` block. Any content
* outside recognized sections is ignored — the `raw` field always
* contains the original markdown for lossless round-tripping.
*/
export function parseMemory(markdown: string): ParsedMemory {
const sections: Record<string, string> = {};
let currentSection: string | null = null;
const sectionLines: string[] = [];

const lines = markdown.split("\n");
for (const line of lines) {
const headingMatch = line.match(/^##\s+(.+)/);
if (headingMatch) {
// Flush previous section
if (currentSection !== null) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This keeps collecting lines after ## Ongoing Context until another ## heading appears, but generateMemory() puts the <!-- MANUAL:START --> ... <!-- MANUAL:END --> block after Ongoing Context without a new heading. As a result, parseMemory(generateMemory()).ongoingContext includes the manual-marker comments, while manualNotes separately extracts the placeholder comment. That diverges from the PR's claim that the generated template parses into empty fields and gives consumers polluted section content.

sections[currentSection] = sectionLines.join("\n").trim();
}
currentSection = headingMatch[1].trim();
sectionLines.length = 0;
} else if (currentSection !== null) {
sectionLines.push(line);
}
}
// Flush last section
if (currentSection !== null) {
sections[currentSection] = sectionLines.join("\n").trim();
}

// Extract manual notes block
const manualMatch = markdown.match(
/<!--\s*MANUAL:START\s*-->([\s\S]*?)<!--\s*MANUAL:END\s*-->/,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parser returns the generated placeholder text (*No user profile configured yet.*, *No facts stored yet.*, *No ongoing context.*) as real structured data. The PR test plan says parseMemory(generateMemory()) should return empty-string fields when no data is stored yet, so consumers will incorrectly treat the default template as populated memory.

);
const manualNotes = manualMatch ? manualMatch[1].trim() : "";

return {
userProfile: sections["User Profile"] ?? "",
keyFacts: sections["Key Facts"] ?? "",
ongoingContext: sections["Ongoing Context"] ?? "",
manualNotes,
raw: markdown,
};
}

export function generateMemory(): string {
Expand Down