Skip to content

Commit 854579a

Browse files
committed
fix: address two Codex P2 findings on PR #56
1. Pass temporal filters to semantic fact recall in the in-process reflective MCP server. phantom_memory_search was building a temporal RecallOptions for episodes when days_back was set, but recallFacts was being called with only { limit }. Reflective skills like mirror that ask for a 7-day window were leaking older facts into the result set. Fix is one word: pass the same opts object to recallFacts. The downstream semantic.recall already honors timeRange via a Qdrant range filter on valid_from. 2. Permit the x-phantom-source provenance marker in the SKILL.md frontmatter schema. The Zod schema was strict and detectSource() in src/skills/storage.ts read frontmatter['x-phantom-source'] without the field being declared, so any built-in skill that set the marker would have been rejected at parse time. Added the field to the schema as an optional enum of "built-in" | "agent" | "user", added the marker to all six built-in SKILL.md files (echo, mirror, overheard, ritual, show-my-tools, thread), and added tests for both the schema acceptance and the source classification. Quality gates: bun test 1044 pass / 0 fail (+4 new tests), bun run lint clean, bun run typecheck clean.
1 parent 96a018f commit 854579a

10 files changed

Lines changed: 77 additions & 1 deletion

File tree

skills-builtin/echo/SKILL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
name: echo
3+
x-phantom-source: built-in
34
description: Before answering a substantive question, quietly check whether the user has already resolved this question in the past.
45
when_to_use: Use when the user asks a substantive question that might have been asked and resolved before. Substantive questions are things like "how should I do X", "what is the right way to Y", "which approach is better", "what did we decide about Z". Do NOT fire on greetings, small talk, status checks, or operational queries like "what time is it" or "are you online". Before deriving a new answer, run a memory similarity check. If a strong prior match exists, surface it inline and ask whether anything has changed.
56
allowed-tools:

skills-builtin/mirror/SKILL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
name: mirror
3+
x-phantom-source: built-in
34
description: Weekly self-audit playback. Surface patterns from the user's past week that they probably cannot see themselves.
45
when_to_use: Use when the user says "mirror", "weekly review", "show me my week", "what did I actually do this week", "reflect on last week", "how did my week go", or any similar reflective request. Also fires automatically on a Friday evening schedule if the user has enabled the mirror ritual.
56
allowed-tools:

skills-builtin/overheard/SKILL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
name: overheard
3+
x-phantom-source: built-in
34
description: Find commitments the user made in the last two weeks and did not follow through on. A promises audit.
45
when_to_use: Use when the user says "overheard", "what did I promise", "what am I behind on", "promises audit", "am I dropping balls", "what did I commit to", "what do I owe people", or any similar commitment-check phrase. Also runs automatically once per day at a user-configured time if enabled.
56
allowed-tools:

skills-builtin/ritual/SKILL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
name: ritual
3+
x-phantom-source: built-in
34
description: Discover recurring behaviors from memory and offer to formalize them as scheduled jobs.
45
when_to_use: Use when the user says "ritual", "what are my patterns", "turn this into a routine", "make this recurring", "what do I do regularly", "what should I schedule", "automate this for me", or any similar pattern-formalization phrase. Also fires on a monthly cadence if enabled.
56
allowed-tools:

skills-builtin/show-my-tools/SKILL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
name: show-my-tools
3+
x-phantom-source: built-in
34
description: List the agent's current skills, memory files, and dashboard URLs. The user-facing discovery path for everything the operator can edit.
45
when_to_use: Use when the user says "what can you do", "what skills do you have", "show me your skills", "what can I edit", "how do I customize you", "what memory files do you have", "what is in your .claude", "where is the dashboard", or any similar discovery question.
56
allowed-tools:

skills-builtin/thread/SKILL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
name: thread
3+
x-phantom-source: built-in
34
description: Show how the user's thinking on a specific topic has evolved over time. A chronological narrative with turning-point callouts.
45
when_to_use: Use when the user says "thread <topic>", "how has my thinking on X evolved", "show me the arc on X", "what have I said about X", "take me through X from the start", "where am I with X", or when the user needs to re-ground in a long-running decision.
56
allowed-tools:

src/agent/in-process-reflective-tools.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@ Each episode includes summary, detail, outcome, started_at, tools_used, and less
8686
results.episodes = await memory.recallEpisodes(input.query, opts).catch(() => []);
8787
}
8888
if (input.memory_type === "semantic" || input.memory_type === "all") {
89-
results.facts = await memory.recallFacts(input.query, { limit }).catch(() => []);
89+
// Same opts so days_back also bounds semantic facts; otherwise a
90+
// weekly mirror leaks 6-month-old preferences into the result.
91+
results.facts = await memory.recallFacts(input.query, opts).catch(() => []);
9092
}
9193

9294
const total = Object.values(results).reduce((sum, arr) => sum + arr.length, 0);

src/skills/__tests__/frontmatter.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,55 @@ describe("parseFrontmatter", () => {
6363
const result = parseFrontmatter(raw);
6464
expect(result.ok).toBe(false);
6565
});
66+
67+
test("accepts x-phantom-source marker for built-in skills", () => {
68+
const raw = `---
69+
name: mirror
70+
x-phantom-source: built-in
71+
description: weekly self-audit
72+
when_to_use: Use on Friday evening.
73+
---
74+
75+
# Mirror
76+
body
77+
`;
78+
const result = parseFrontmatter(raw);
79+
expect(result.ok).toBe(true);
80+
if (!result.ok) return;
81+
expect(result.parsed.frontmatter["x-phantom-source"]).toBe("built-in");
82+
});
83+
84+
test("accepts x-phantom-source marker for agent-authored skills", () => {
85+
const raw = `---
86+
name: mirror
87+
x-phantom-source: agent
88+
description: weekly self-audit
89+
when_to_use: Use on Friday evening.
90+
---
91+
92+
# Mirror
93+
body
94+
`;
95+
const result = parseFrontmatter(raw);
96+
expect(result.ok).toBe(true);
97+
if (!result.ok) return;
98+
expect(result.parsed.frontmatter["x-phantom-source"]).toBe("agent");
99+
});
100+
101+
test("rejects invalid x-phantom-source values", () => {
102+
const raw = `---
103+
name: mirror
104+
x-phantom-source: bogus
105+
description: weekly self-audit
106+
when_to_use: Use on Friday evening.
107+
---
108+
109+
# Mirror
110+
body
111+
`;
112+
const result = parseFrontmatter(raw);
113+
expect(result.ok).toBe(false);
114+
});
66115
});
67116

68117
describe("serializeSkill", () => {

src/skills/__tests__/storage.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,19 @@ describe("listSkills", () => {
4141
const result = listSkills();
4242
expect(result.skills.length).toBe(1);
4343
expect(result.skills[0].name).toBe("mirror");
44+
expect(result.skills[0].source).toBe("user");
45+
});
46+
47+
test("classifies a skill with x-phantom-source: built-in as built-in", () => {
48+
const skillDir = join(tmp, "mirror");
49+
mkdirSync(skillDir);
50+
writeFileSync(
51+
join(skillDir, "SKILL.md"),
52+
"---\nname: mirror\nx-phantom-source: built-in\ndescription: weekly\nwhen_to_use: Use on Friday.\n---\n\n# Mirror\n",
53+
);
54+
const result = listSkills();
55+
expect(result.skills.length).toBe(1);
56+
expect(result.skills[0].source).toBe("built-in");
4457
});
4558

4659
test("skips directories with bad names", () => {

src/skills/frontmatter.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export const SKILL_NAME_PATTERN = /^[a-z][a-z0-9-]{0,63}$/;
3232
export const MAX_BODY_BYTES = 50 * 1024; // 50 KB
3333

3434
export const SkillContextSchema = z.enum(["inline", "fork"]);
35+
export const SkillSourceSchema = z.enum(["built-in", "agent", "user"]);
3536

3637
export const SkillFrontmatterSchema = z
3738
.object({
@@ -46,6 +47,11 @@ export const SkillFrontmatterSchema = z
4647
arguments: z.array(z.string().min(1)).optional(),
4748
context: SkillContextSchema.optional(),
4849
"disable-model-invocation": z.boolean().optional(),
50+
// Provenance marker. Omitted on user-authored skills (default treats
51+
// missing as "user"). Built-in skills shipped under skills-builtin/ set
52+
// this to "built-in" so the dashboard can group and badge them.
53+
// detectSource() in src/skills/storage.ts reads this field.
54+
"x-phantom-source": SkillSourceSchema.optional(),
4955
})
5056
.strict();
5157

0 commit comments

Comments
 (0)