Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 2 additions & 1 deletion Memory/src/service/retrieval/retrieval-service.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { skillInjectionDescription } from "./skill-injection-description.js";
import type {
RetrievalQueryExtract,
RetrievalResult
Expand Down Expand Up @@ -870,7 +871,7 @@ function renderInjectedSnippet(
"",
...labeledInjectedBlock("Name", name),
"",
...labeledInjectedBlock("Description", firstLineSummary(guide, summaryChars) || "(not provided)")
...labeledInjectedBlock("Description", skillInjectionDescription({ name, guide, retrievalBlurb: skill?.retrievalBlurb, triggerContext: skill?.triggerContext, maxChars: summaryChars }))
];
return {
refKind: "skill",
Expand Down
64 changes: 64 additions & 0 deletions Memory/src/service/retrieval/skill-injection-description.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { parse as parseYaml } from "yaml";
import { isRecord } from "../../utils/json.js";

/** Select a useful summary without treating the guide title or YAML as prose. */
export function skillInjectionDescription(input: {
name: string;
guide: string;
retrievalBlurb?: string;
triggerContext?: string;
maxChars: number;
}): string {
const normalize = (value: string) => value.replace(/\s+/gu, " ").trim();
const usable = (value: unknown): string | undefined => {
if (typeof value !== "string") return undefined;
const text = normalize(value);
return text && text !== normalize(input.name) ? text : undefined;
};
let description = usable(input.retrievalBlurb) ?? usable(input.triggerContext);
let body = input.guide.replace(/^\uFEFF/u, "").trim();
if (!description) {
const frontmatter = body.match(/^---\r?\n([\s\S]*?)\r?\n(?:---|\.\.\.)(?:\r?\n|$)/u);
if (frontmatter) {
body = body.slice(frontmatter[0].length).trim();
try {
const parsed: unknown = parseYaml(frontmatter[1]!, { maxAliasCount: 0 });
if (isRecord(parsed)) description = usable(parsed.description);
} catch {
// A malformed header must not become the description or hide valid prose.
}
} else if (/^---(?:\r?\n|$)/u.test(body)) {
// An unterminated header has no safe prose boundary.
body = "";
}
}
if (!description) {
const lines = body.split(/\r?\n/u);
const paragraph: string[] = [];
let fence: string | undefined;
for (let index = 0; index < lines.length; index += 1) {
const line = lines[index]!.trim();
const marker = line.match(/^(`{3,}|~{3,})/u)?.[1];
if (marker) {
if (!fence) fence = marker[0];
else if (marker[0] === fence) fence = undefined;
continue;
}
if (fence) continue;
const isHeading = /^#{1,6}(?:\s|$)/u.test(line)
|| /^(?:=+|-+)\s*$/u.test(lines[index + 1]?.trim() ?? "")
|| /^(?:=+|-+)$/u.test(line)
|| /^\*\*[^*]+\*\*:?$/u.test(line);
if (!line || isHeading) {
if (paragraph.length) break;
continue;
}
if (!paragraph.length && !usable(line)) continue;
paragraph.push(line);
}
description = usable(paragraph.join(" "));
}
if (!description) return "(not provided)";
const limit = Math.max(1, Math.floor(input.maxChars));
return description.length <= limit ? description : `${description.slice(0, limit - 1)}…`;
}
43 changes: 43 additions & 0 deletions Memory/tests/service/retrieval/skill-description.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { describe, expect, it } from "vitest";
import { buildInjectedContext } from "../../../src/service/retrieval/retrieval-service.js";
import type { MemoryRow, RecallHit } from "../../../src/types.js";

function skill(id: string, guide: string, procedure: Record<string, unknown> = {}) {
const hit: RecallHit = { id, kind: "skill", memoryLayer: "Skill", status: "activated", title: id,
snippet: guide, score: 1, tags: [], source: "skill" };
const memory: MemoryRow = { id, timeline: "2026-10-28T00:00:00Z", userId: "test", memoryType: "SkillMemory",
status: "activated", visibility: "private", memoryKey: id, memoryValue: guide, tags: [], info: {},
properties: { internal_info: { memory_layer: "Skill", memory_kind: "skill",
skill: { name: id, invocation_guide: guide, procedure_json: procedure } } },
memoryLayer: "Skill", version: 1, createdAt: "2026-10-28T00:00:00Z", updatedAt: "2026-10-28T00:00:00Z" };
return { hit, memory };
}

describe("Skill injection descriptions", () => {
it.each([
["generated", "# report-writer\n\nWrite a repair report.", {}, "Write a repair report."],
["blurb priority", "# report-writer\n\nBody fallback", { retrievalBlurb: "Preferred summary", triggerContext: "Trigger fallback" }, "Preferred summary"],
["trigger fallback", "# report-writer\n\nBody fallback", { retrievalBlurb: " ", triggerContext: "Trigger summary" }, "Trigger summary"],
["frontmatter", "---\nname: report-writer\ndescription: Write a repair report.\n---\n# report-writer", {}, "Write a repair report."],
["multiline YAML", "---\r\nname: report-writer\r\ndescription: >-\r\n Write a report\r\n with evidence.\r\n---\r\n# report-writer", {}, "Write a report with evidence."],
["skip title and name", "# report-writer\n\nreport-writer\n\n## Usage\nExplain verified changes.", {}, "Explain verified changes."],
["missing", "# report-writer\n\nreport-writer", {}, "(not provided)"],
["invalid YAML", "---\nname: [broken\n---\n# report-writer\n\nBody survives.", {}, "Body survives."]
])("uses the expected description for %s", (_label, guide, procedure, expected) => {
const { hit, memory } = skill("report-writer", guide, procedure);
const packet = buildInjectedContext([hit], 10000, [memory]);
const content = packet.injectedContext.sections[0]!.content;
expect(content).toContain(`Description:\n${expected}`);
expect(packet.sourceMemoryIds).toEqual([hit.id]);
});

it("keeps full guides and applies the summary length limit", () => {
const guide = "# report-writer\n\nComplete procedure text.";
const { hit, memory } = skill("report-writer", guide, { retrievalBlurb: "x".repeat(400) });
const full = buildInjectedContext([hit], 10000, [memory], "search", undefined, undefined, { skillInjectionMode: "full" });
expect(full.injectedContext.sections[0]!.content).toContain(guide);
expect(full.injectedContext.sections[0]!.content).not.toContain("x".repeat(20));
const summary = buildInjectedContext([hit], 10000, [memory], "search", undefined, undefined, { skillSummaryChars: 20 });
expect(summary.injectedContext.sections[0]!.content).toContain("x".repeat(19) + "…");
});
});
Loading