Sprint Brief: Procedural Memory P1
Schema + Enrichment + Node Creation
What You’re Building
Skills become first-class nodes in the knowledge graph. When a skill is installed, it gets an entity, embeddings, and metadata so the agent can discover it through memory retrieval — not just filesystem search.
Required Reading
docs/specs/INDEX.md— read the Cross-Cutting Invariants section first. These override anything in the individual spec.docs/specs/approved/procedural-memory-plan.md— Phase P1 (section 10)docs/KNOWLEDGE-ARCHITECTURE.md— conceptual north star for how entities, aspects, and attributes work
Cross-Cutting Rules (from INDEX)
- agent_id on every new table.
skill_metamust have anagent_idcolumn. Skills are scoped per-agent in the graph even though the filesystem pool is shared. Each agent gets its own skill entity with its own usage stats, decay, and edges. - Entity type taxonomy is canonical. Skills use
entity_type = 'skill'. Do not invent new types. - Importance is structural. The
importanceOnInstalldefault (0.7) is a cold-start value. Once KA tables exist, importance will be computed from structural density. Designskill_metato support both.
Deliverables
1. Migration: skill_meta table
New migration file in packages/core/src/migrations/. Check
migrations/index.ts for the next available number.
CREATE TABLE skill_meta (
entity_id TEXT PRIMARY KEY REFERENCES entities(id),
agent_id TEXT NOT NULL DEFAULT 'default',
version TEXT,
author TEXT,
license TEXT,
source TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'utility',
triggers TEXT,
tags TEXT,
permissions TEXT,
enriched INTEGER DEFAULT 0,
installed_at TEXT NOT NULL,
last_used_at TEXT,
use_count INTEGER DEFAULT 0,
decay_rate REAL DEFAULT 0.99,
fs_path TEXT NOT NULL,
uninstalled_at TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX idx_skill_meta_agent ON skill_meta(agent_id);
CREATE INDEX idx_skill_meta_source ON skill_meta(source);
2. Frontmatter enrichment
When a skill is installed and its frontmatter is thin (description
<30 chars or no triggers field), run an LLM pass to generate:
- Rich description (1-2 sentences, mechanism + use-case)
triggerslist (3-8 discovery phrases)tagsfor domain grouping
Use the existing LlmProvider from
packages/daemon/src/pipeline/provider.ts. Same model config as the
extraction worker. New prompt function alongside
extractFactsAndEntities().
Critical: Use YAML round-trip parse/serialize for frontmatter rewrites. Not regex. Preserve existing fields and comments.
3. Skill entity creation
On install:
- Parse SKILL.md frontmatter
- Enrich if needed (step 2 above)
- Create entity row:
entity_type = 'skill',id = 'skill:{name}' - Create
skill_metarow with installation metadata - Generate embedding from enriched frontmatter only:
name + description + triggers. NOT the body. - Store embedding via existing
embeddingspipeline withsource_type = 'skill' - Run extraction on SKILL.md body for entity references and explicit skill relations
- Set initial importance to
importanceOnInstall(default 0.7)
4. Skill uninstallation
On remove:
- Remove filesystem artifact (existing behavior)
- Remove skill relation edges from
relations - Remove skill mention links from
memory_entity_mentions - Hard-delete entity row +
skill_metarow in same write transaction
5. Filesystem reconciler
- Startup backfill: scan
~/.agents/skills/*/SKILL.md, create nodes for any installed skill missing from graph - Periodic reconciler: same scan on interval (
reconcileIntervalMs, default 60000) - File watcher: watch
~/.agents/skills/**/SKILL.mdfor low-latency reconciliation - If graph node exists but file is missing: execute uninstall flow
- Idempotent: matched by canonical name + frontmatter hash
6. Configuration
Add under PipelineV2Config in packages/core/src/types.ts:
readonly procedural?: {
readonly enabled: boolean; // default true
readonly decayRate: number; // default 0.99
readonly minImportance: number; // default 0.3
readonly importanceOnInstall: number; // default 0.7
readonly enrichOnInstall: boolean; // default true
readonly enrichMinDescription: number; // default 30
readonly reconcileIntervalMs: number; // default 60000
};
Wire defaults in packages/daemon/src/memory-config.ts.
Key Files
packages/core/src/migrations/— new migration goes herepackages/core/src/migrations/index.ts— register migrationpackages/core/src/types.ts— add procedural configpackages/core/src/skills.ts— existing skill registrypackages/daemon/src/memory-config.ts— wire config defaultspackages/daemon/src/pipeline/provider.ts— LlmProvider for enrichmentpackages/daemon/src/pipeline/decision.ts— existing extraction promptspackages/daemon/src/db-accessor.ts— ReadDb/WriteDb typed accessorpackages/daemon/src/db-helpers.ts— vector blob helperspackages/daemon/src/daemon.ts— file watcher, API routes
What NOT to Build (P2+)
/api/skills/usedendpoint (P2)/api/skills/suggestendpoint (P4)- Skill-to-skill affinity computation (P3)
- Dashboard skill graph visualization (P5)
?ranked=trueon skill listing (P4)
Verification
bun run build— no type errorsbun test— existing tests passbun run typecheck— clean- Install a skill, verify entity + skill_meta + embedding created
- Uninstall a skill, verify hard-delete cleanup
- Restart daemon, verify backfill creates nodes for existing skills
- Delete a SKILL.md file, verify reconciler uninstalls the graph node
- Verify enrichment writes back to SKILL.md frontmatter (YAML roundtrip)
- Verify
agent_id = 'default'on all created rows