Skip to content

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.

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, verify

The 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.

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:

skillactivates whentags
memoturn-mcp-toolkitAlways — reference for which MCP tool to calluniversal
next-app-router-conventionsNext.js App Router routes, layouts, server componentsnext, react
cloudflare-workers-tailDebugging, tailing, deploying Workerscloudflare, workers
drizzle-migrationsSchema changes, new tables, column alterationsdrizzle, postgres
python-fastapi-uvicornFastAPI routes, Pydantic models, async patternspython, fastapi
monorepo-pnpm-turboCross-package changes, turborepo pipelinesmonorepo, pnpm, turbo

You can also write your own and install them directly. Skills are just YAML frontmatter + markdown — no SDK needed.

CLAUDE.md is great for a single tool. Skills add three things:

  1. Cross-tool reach. Cursor, Codex, and custom agents see the same instructions as Claude Code.
  2. Contextual activation. The when_to_use field means agents load skills only when relevant — no system prompt bloat.
  3. Persistence in the memory layer. Skills survive across sessions, machines, and git branches. They’re searchable via search_memory(mode: "skills").
deploy-runbook/
├── SKILL.md # frontmatter + markdown body, required
├── scripts/
│ └── pre-flight.sh
├── references/
│ └── rollback.md
└── assets/
└── topology.png

SKILL.md opens with a YAML frontmatter block:

---
name: deploy-runbook
description: |
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: MIT
compatibility: cloudflare-workers >= 3
metadata:
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 markdown

Core 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.

Terminal window
# install or update from a directory
memoturn skill install ./skills/deploy-runbook
# install a single SKILL.md (no bundled files)
memoturn skill install ./skills/quick-tip/SKILL.md

The 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.

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.

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.

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.

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.

Soft-delete by name:

Terminal window
memoturn skill forget deploy-runbook

The 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.

layerwhat’s there
Postgres skillsmanifest jsonb (full validated frontmatter), description, lifecycle timestamps, soft-delete marker, embedding pointer
R2 ${slug}/${name}/SKILL.mdthe SKILL.md text verbatim, frontmatter included
R2 ${slug}/${name}/<path>every bundled file at its original relative path
Vectorize kind=skilldescription 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.

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:

Terminal window
memoturn skill registry list # everything published
memoturn skill registry search <query> # filter by name or description
memoturn skill install <name> # install a published skill by name
// promote a project skill to the public registry
await 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.