Agent Skills
Every coding tool has its own way to give agents instructions: Claude Code has .claude/skills/, Cursor has .cursor/rules/, Codex has AGENTS.md. The problem is that these are tool-specific, filesystem-local, and invisible to every other agent on the project.
Memoturn skills solve this. A skill is a project-level instruction bundle stored in the memory layer — not in any tool’s dotfiles. Every agent connecting via MCP discovers the same skills, regardless of whether it’s Claude Code, Cursor, Codex, or a custom agent. Skills persist across sessions, across machines, and across tools.
How they work
Section titled “How they work” onboard scans repo → detects Next.js + Drizzle + Cloudflare Workers → recommends: next-app-router-conventions, drizzle-migrations, cloudflare-workers-tail → install_skill writes each to Postgres + R2 + Vectorize
agent connects via MCP → calls list_skills → sees 3 skills with descriptions + when_to_use → user asks "add a new migration" → agent reads when_to_use for drizzle-migrations: "schema changes, new tables, column alterations" → calls get_skill("drizzle-migrations") → loads the full instructions → follows the runbook: generate migration, apply, verifyThe progressive-disclosure model keeps it cheap: list_skills returns names + descriptions (small), agents only call get_skill to load the full body when a skill matches. Bundled files (scripts/, references/) are loaded on demand via get_skill_file.
Default catalog
Section titled “Default catalog”Memoturn ships 6 built-in skills. The onboarding flow (memoturn onboard or the dashboard) scans your repo, detects the stack, and recommends the matching ones:
| skill | activates when | tags |
|---|---|---|
memoturn-mcp-toolkit | Always — reference for which MCP tool to call | universal |
next-app-router-conventions | Next.js App Router routes, layouts, server components | next, react |
cloudflare-workers-tail | Debugging, tailing, deploying Workers | cloudflare, workers |
drizzle-migrations | Schema changes, new tables, column alterations | drizzle, postgres |
python-fastapi-uvicorn | FastAPI routes, Pydantic models, async patterns | python, fastapi |
monorepo-pnpm-turbo | Cross-package changes, turborepo pipelines | monorepo, pnpm, turbo |
You can also write your own and install them directly. Skills are just YAML frontmatter + markdown — no SDK needed.
Why not just use CLAUDE.md?
Section titled “Why not just use CLAUDE.md?”CLAUDE.md is great for a single tool. Skills add three things:
- Cross-tool reach. Cursor, Codex, and custom agents see the same instructions as Claude Code.
- Contextual activation. The
when_to_usefield means agents load skills only when relevant — no system prompt bloat. - Persistence in the memory layer. Skills survive across sessions, machines, and git branches. They’re searchable via
search_memory(mode: "skills").
Anatomy of a skill
Section titled “Anatomy of a skill”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 the API worker.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.
Publish to the registry
Section titled “Publish to the registry”Skills can be shared publicly via publish_skill / unpublish_skill. The registry is a global namespace — first project to publish a name owns it.
Browsing is a CLI and API surface, not a web page — /v1/registry/* is public and unauthenticated:
memoturn skill registry list # everything publishedmemoturn skill registry search <query> # filter by name or descriptionmemoturn skill install <name> # install a published skill by name// promote a project skill to the public registryawait mt.mcp("publish_skill", { name: "deploy-runbook" });
// withdraw from the registry (project skill unaffected)await mt.mcp("unpublish_skill", { name: "deploy-runbook" });Other projects can browse published skills via list_default_skills and install them with install_skill.