Agent Skills
Memoturn implements the agentskills.io v1 spec for per-project skill bundles. A “skill” is a SKILL.md file (frontmatter + body) plus optional supporting files (scripts/, references/, assets/, anything). Agents discover skills via name + description, load the body when relevant, then read individual bundled files on demand.
The progressive-disclosure model: cheap to list, only pay the body cost when a skill matches.
Anatomy of a skill
Section titled “Anatomy of a skill”deploy-runbook/├── SKILL.md # frontmatter + markdown body — required├── scripts/│ └── pre-flight.sh├── references/│ └── rollback.md└── assets/ └── topology.pngSKILL.md opens with a YAML frontmatter block:
---name: deploy-runbookdescription: | Coordinated procedure for shipping a worker change to staging, soaking, and rolling forward to production. Use when the change touches a Cloudflare binding or migrates the schema.license: MITcompatibility: cloudflare-workers >= 3metadata: team: platform oncall-channel: "#deploys"when_to_use: | Any code change that ships to api.memoturn.ai.allowed-tools: - bash - filesystem---
# Deploy Runbook
1. Run `scripts/pre-flight.sh`2. ... full body in markdownCore spec fields (name, description, license, compatibility, metadata, allowed-tools) are validated strictly. Tool-specific extension fields (Anthropic’s when_to_use, paths, hooks, disable-model-invocation, etc.) round-trip verbatim — store anything your agent runtime understands.
Install
Section titled “Install”# install or update from a directorymemoturn skill install ./skills/deploy-runbook
# install a single SKILL.md (no bundled files)memoturn skill install ./skills/quick-tip/SKILL.mdThe CLI walks the directory, reads SKILL.md + every non-hidden file under it (paths preserved relative to the skill root), validates the frontmatter, and uploads. Files larger than 1MB are rejected.
import { readFileSync } from "node:fs";import { Memoturn } from "@memoturn/sdk";
const mt = new Memoturn({ apiKey: process.env.MEMOTURN_KEY!, projectId: "my-project" });
await mt.installSkill({ skill_md: readFileSync("./skills/deploy-runbook/SKILL.md", "utf-8"), files: { "scripts/pre-flight.sh": readFileSync("./skills/deploy-runbook/scripts/pre-flight.sh", "utf-8"), "references/rollback.md": readFileSync("./skills/deploy-runbook/references/rollback.md", "utf-8"), },});// any MCP-aware client can call install_skill directly{ "tool": "install_skill", "arguments": { "skill_md": "---\nname: deploy-runbook\n...", "files": { "scripts/pre-flight.sh": "..." } }}Re-installing the same name updates in place — Postgres is upsert + R2 is overwrite. Soft-deleted skills (via forget_skill) don’t block reuse of their name; the partial unique index only covers active rows.
Discover
Section titled “Discover”Two surfaces: enumerate everything cheaply, or semantic search by description.
const { skills } = await mt.listSkills();for (const s of skills) { console.log(`${s.name}\t${s.description}`);}Returns the discovery layer — name + description + a few manifest surface fields per active skill. Cheap; this is what agents call up-front before deciding what to load.
const { hits } = await mt.searchMemory({ query: "rolling deploy with health-check soak", mode: "skills", k: 5,});Hybrid: dense (Vectorize, embeddings over the description) + lexical (Postgres FTS, also over the description), fused via reciprocal rank fusion. Excluded from mode=auto to keep memory recall and skill discovery distinct surfaces.
Once you’ve picked a skill, load the body:
const skill = await mt.getSkill("deploy-runbook");if (skill) { console.log(skill.manifest.description); console.log(skill.body); // full markdown body, no frontmatter}Returns null if the skill doesn’t exist (or has been forgotten) so callers can branch on presence without try/catch.
Read bundled files on demand
Section titled “Read bundled files on demand”Skills can ship arbitrary supporting files. Agents read them only when actually needed — keeps the activation cost low even for large bundles:
const file = await mt.getSkillFile("deploy-runbook", "scripts/pre-flight.sh");if (file) { console.log(`${file.size} bytes:\n${file.content}`);}Path traversal is server-side rejected — leading /, \, .., and reserved SKILL.md are all 400’d. Paths are POSIX-style relative to the skill root regardless of the originating OS.
Forget
Section titled “Forget”Soft-delete by name:
memoturn skill forget deploy-runbookThe Postgres row gets forgotten_at set and the Vectorize entry is dropped, so list_skills and mode=skills searches no longer surface it. Bundled R2 files are intentionally retained — recovery is one schema-level update if you change your mind. A subsequent install_skill with the same name creates a fresh active row alongside the tombstone.
How it stores
Section titled “How it stores”| layer | what’s there |
|---|---|
Postgres skills | manifest jsonb (full validated frontmatter), description, lifecycle timestamps, soft-delete marker, embedding pointer |
R2 ${slug}/${name}/SKILL.md | the SKILL.md text verbatim, frontmatter included |
R2 ${slug}/${name}/<path> | every bundled file at its original relative path |
Vectorize kind=skill | description embedding for the dense leg of mode=skills |
Writes are serialized through the per-project Durable Object so install/forget against the same name don’t race. The partial unique index on (project_id, name) WHERE forgotten_at IS NULL is the second line of defense.