// core/skills.js — SELF-EVOLVING AGENT SKILLS, 100% agentskills.io-compatible, enhanced by the UOR // substrate. A skill is a folder with SKILL.md (name + description frontmatter + instructions) — // the open Anthropic/agentskills.io format, portable to any skills-compatible agent. The Hermes // idea (the agent autonomously CREATES and IMPROVES skills from experience) is realized as the // agent's own tools. The substrate enhancement: every skill version is content-addressed (κ) and // chained — a tamper-proof PROVENANCE of how the skill evolved (Holo Prov). Skills live in OPFS // "/skills//SKILL.md"; the chain log in "/skills//.prov" (one κ per version). // // Progressive disclosure (the spec): DISCOVERY (names+descriptions injected at session start) → // ACTIVATION (read_skill loads full SKILL.md) → EXECUTION (the agent follows it, using file tools). import { skillAsHowTo, verifySemantic } from "./semantic.js"; const te = new TextEncoder(), td = new TextDecoder(); const sha = async (s) => "did:holo:sha256:" + [...new Uint8Array(await crypto.subtle.digest("SHA-256", te.encode(s)))].map((b) => b.toString(16).padStart(2, "0")).join(""); const slug = (s) => String(s || "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "skill"; async function skillsRoot() { const d = await navigator.storage.getDirectory(); const a = await d.getDirectoryHandle("skills", { create: true }); return a; } async function skillDir(name, create = false) { return (await skillsRoot()).getDirectoryHandle(slug(name), { create }); } async function readText(dir, file) { const fh = await dir.getFileHandle(file); return td.decode(await (await fh.getFile()).arrayBuffer()); } async function writeText(dir, file, text) { const fh = await dir.getFileHandle(file, { create: true }); const w = await fh.createWritable(); await w.write(te.encode(text)); await w.close(); } // SKILL.md = YAML-ish frontmatter (name, description) + markdown instructions (verbatim spec form). function buildSkillMd({ name, description, instructions }) { return `---\nname: ${name}\ndescription: ${description}\n---\n\n${instructions || ""}\n`; } function parseSkillMd(text) { const m = text.match(/^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/); const meta = {}; if (m) for (const line of m[1].split("\n")) { const i = line.indexOf(":"); if (i > 0) meta[line.slice(0, i).trim()] = line.slice(i + 1).trim(); } return { name: meta.name || "", description: meta.description || "", instructions: m ? m[2].trim() : text.trim() }; } export async function listSkills() { const out = []; try { const root = await skillsRoot(); for await (const [n, h] of root.entries()) { if (h.kind !== "directory") continue; try { const sk = parseSkillMd(await readText(h, "SKILL.md")); out.push({ slug: n, name: sk.name || n, description: sk.description }); } catch {} } } catch {} return out.sort((a, b) => a.name.localeCompare(b.name)); } export async function readSkill(name) { const dir = await skillDir(name); return parseSkillMd(await readText(dir, "SKILL.md")); } // save = create OR improve (self-evolution). Appends a κ to the provenance chain — every version // sealed + chained to its parent, so the skill's evolution is verifiable (Holo Prov shape). export async function saveSkill({ name, description, instructions }) { const dir = await skillDir(name, true); const md = buildSkillMd({ name, description, instructions }); const kappa = await sha(md); let prov = []; try { prov = JSON.parse(await readText(dir, ".prov")); } catch {} const parent = prov.length ? prov[prov.length - 1].kappa : null; prov.push({ v: prov.length + 1, kappa, parent, descKappa: await sha(description || "") }); await writeText(dir, "SKILL.md", md); await writeText(dir, ".prov", JSON.stringify(prov, null, 1)); // semantic skin (C2): emit the W3C linked-data view — schema:HowTo + PROV-O revision chain. const ld = skillAsHowTo({ name, description, instructions, kappa, prov }); await writeText(dir, "skill.jsonld", JSON.stringify(ld, null, 1)); return { slug: slug(name), kappa, version: prov.length, parent, semantic: verifySemantic(ld).ok }; } // the W3C linked-data view of a skill (schema:HowTo + PROV-O), for export / validation / agents. export async function skillLinkedData(name) { try { return JSON.parse(await readText(await skillDir(name), "skill.jsonld")); } catch { return null; } } export async function skillProvenance(name) { try { return JSON.parse(await readText(await skillDir(name), ".prov")); } catch { return []; } } // DISCOVERY block (progressive disclosure stage 1): names + descriptions only — small footprint. export async function skillsDiscoveryPrompt() { const sk = await listSkills(); if (!sk.length) return ""; return "You have these learned SKILLS available (each a verifiable, content-addressed capability you built from experience). " + "When a task matches a skill's description, call read_skill to load its full instructions, then follow them. " + "When you complete a non-trivial task worth reusing, call save_skill to capture it (or improve an existing one):\n" + sk.map((s) => `- ${s.name}: ${s.description}`).join("\n"); } // the agent's skill tools (Hermes self-evolution: the agent creates/improves/uses skills itself) export function skillTools() { return [ { def: { name: "list_skills", description: "List the learned skills available (name + description).", inputSchema: { type: "object", properties: {}, required: [] } }, serverName: "skills", call: async () => { const s = await listSkills(); return { text: s.length ? s.map((x) => `${x.name}: ${x.description}`).join("\n") : "(no skills yet)", isError: false }; }, }, { def: { name: "read_skill", description: "Load a skill's full instructions by name, to follow them for the current task.", inputSchema: { type: "object", properties: { name: { type: "string" } }, required: ["name"] } }, serverName: "skills", call: async ({ name }) => { try { const s = await readSkill(name); return { text: `# ${s.name}\n${s.description}\n\n${s.instructions}`, isError: false }; } catch (e) { return { text: "no such skill: " + name, isError: true }; } }, }, { def: { name: "save_skill", description: "Capture a reusable skill from what you just did (or improve an existing one). name: short; description: one line of WHEN to use it; instructions: the step-by-step procedure. Each save is sealed + chained (verifiable provenance).", inputSchema: { type: "object", properties: { name: { type: "string" }, description: { type: "string" }, instructions: { type: "string" } }, required: ["name", "description", "instructions"] } }, serverName: "skills", call: async ({ name, description, instructions }) => { try { const r = await saveSkill({ name, description, instructions }); return { text: `skill "${name}" saved (v${r.version}, ${r.kappa.slice(0, 28)}…${r.parent ? `, evolved from ${r.parent.slice(0, 20)}…` : ", first version"})`, isError: false }; } catch (e) { return { text: "save error: " + (e.message || e), isError: true }; } }, }, ]; }