Files
poimen-memory/tasks/M4.1-skill-draft.md

6.1 KiB
Raw Permalink Blame History

M4.1 — mem skill draft

Field Value
Phase M4 — Skills
Size M — 13 days
Status 🟡 In progress — render_skill() and mem materialize implemented
Flags
Spec inlined below
Blocks M3.1

Goal

Turn a memory note into a draft skill — the step that makes the memory do something rather than only be read.

Existing code (already implemented)

crates/mem-core/src/lesson.rs already contains:

Function What it does Tests
render_skill(tool, lessons) Generates SKILL.md with YAML frontmatter (name, description), per-lesson sections with seen/last_seen/confidence/resolution, and recurring-failure warnings skill_description_lists_symptoms_not_summary
render_injection(hit, max_chars) Generates capped injection text for prompts injection_is_capped

crates/mem-cli/src/lessons_cmd.rs already contains:

Command What it does
mem materialize Writes skills/<tool>-failures/SKILL.md per tool + MEMORY.md digest. Creates dirs, prints symlink instructions for Claude Code / pi.

What remains to complete this task

The existing code generates skills from command-level lessons (Lesson struct). This task requires:

  1. Draft from L1/L2 memory notesmem skill draft --from <project>/<query-id> reads a GRU-Mem memory node, not a lesson
  2. LLM-assisted conversion — prompt the model to convert descriptive memory into procedural instruction using the rubric
  3. _drafts/ enforcement — existing mem materialize writes directly to skills/; this task must write to _drafts/ only
  4. generated_from: <sha> provenance — link back to the memory node
  5. Integration teststests/it_skill_draft.rs (7 assertions)

Files

Action Path
Exists crates/mem-core/src/lesson.rsrender_skill()
Exists crates/mem-cli/src/lessons_cmd.rsmem materialize
Modify crates/mem-cli/src/main.rs — add Commands::Skill { Draft } subcommand
Create tests/it_skill_draft.rs — integration tests (7 assertions)

Facts (inlined — no spec read needed)

mem skill draft --from poimen/infra-root-causes
-> vault/skills/_drafts/poimen-infra-root-causes/SKILL.md

A skill is a projection, not a level. L0/L1/L2 are descriptive — what happened. A skill is procedural — what to do next time. The gated loop does not produce it: "does this chunk contain evidence for Q" has no meaning when the output is an instruction.

Format is free, because SKILL.md is YAML frontmatter plus markdown, which is exactly an Obsidian note. Verified against a real installed skill:

---
name: keyword-research
description: 'Use when the user asks to "find keywords"... Not for X — use Y.'
when_to_use: "Use when starting keyword research for a new page..."
argument-hint: "<topic or seed keyword> [market/language]"
---

So the same file is a vault note and a loadable skill, with no conversion.

description is the whole game. It is the trigger — a skill whose description does not match how the user actually phrases the request never fires, no matter how good the body is. Note the real example above spends half its description on negative routing ("Not for X — use Y").

Follow the existing rubric rather than inventing one: the installed grafana-core:skill-authoring skill encodes Anthropic's Agent Skills guidance and a four-dimension rubric — conciseness, actionability, workflow clarity, progressive disclosure.

Drafts land in _drafts/ and are never auto-loaded. A directory, not a frontmatter flag, because a directory cannot be accidentally globbed into --skill.

Steps

  1. mem skill draft --from <project>/<query-id> reads the L1 or L2 note.
  2. Prompt the model to convert descriptive memory into procedural instruction, with the rubric's four dimensions in the prompt.
  3. Emit frontmatter: name, description, when_to_use, plus generated_from: <sha> and generated_at.
  4. Write to vault/skills/_drafts/<project>-<query-id>/SKILL.md.
  5. Refuse to write outside _drafts/. Promotion is a human git mv.
  6. --dry-run prints without writing.

Acceptance

  • Output parses as valid frontmatter + markdown.
  • generated_from resolves to a real node sha.
  • The file lands in _drafts/ and nowhere else.
  • Promotion is not automated anywhere in the codebase.

Verify

Harness: seeded vault and log; scripted model client for determinism.

Integration testtests/it_skill_draft.rs:

  1. a1_valid_frontmatter — parse the output; assert name, description, when_to_use, generated_from present and non-empty.
  2. a2_generated_from_resolves — the sha exists in memory_node.
  3. a3_writes_only_to_drafts — assert the path contains _drafts/; attempt to pass a path outside it and assert refusal.
  4. a4_no_promotion_path — grep the workspace for any code writing to vault/skills/ that is not under _drafts/; assert none. Promotion must be manual.
  5. a5_description_is_trigger_shaped — assert description contains at least one phrasing cue (a quoted user phrase or "Use when"), matching the installed examples.
  6. a6_dry_run_writes_nothing — assert no file created.
  7. a7_idempotent — same input twice produces identical bytes apart from generated_at.

Command: cargo test --test it_skill_draft

False pass:

  • Asserting the file was written without asserting where. The entire safety property of this task is the location, and a draft written to vault/skills/ is immediately loadable.
  • Accepting any non-empty description. A one-line restatement of the title never triggers, so the feature appears to work and the skill never fires — assertion 5 is a weak but real guard.

Traps

  • Auto-promoting "when the draft looks good". That closes the loop this design deliberately leaves open, and there is no external verifier inside it.
  • Generating the body from L0 evidence. Skills are procedure distilled from synthesis; raw transcript produces a narrative, not an instruction.

Background: DESIGN.md — Skills, the procedural projection