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, and 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) plus 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, keeping 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, so 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.