Features

Skills

Extend agents with portable markdown instructions and optional tool code. Compatible with Anthropic Agent Skills.

A skill is a portable unit of instruction that extends an agent's knowledge or capabilities. Appstrate skills implement Anthropic's Agent Skills format: a folder containing a SKILL.md file with YAML frontmatter.

A skill authored for Claude (Claude Code, the Messages API, the Agent SDK) runs in Appstrate without modification. Appstrate adds one layer on top: AFPS packaging (ZIP + semver + registry + SRI integrity) so skills can be versioned, shared across tenants, and pinned by agents.

Via the UI

Open the dashboard → Skills in the sidebar (/skills). From there you can:

  • Browse the list of system and org-owned skills, search, and open a detail page
  • Create a new skill (/skills/new) — opens the package editor with a SKILL.md template
  • Import an existing .afps ZIP via the import modal (drag-and-drop or file picker), including GitHub repo imports
  • Open a detail page (/skills/:scope/:name) — source viewer, version history, diff against latest published version, and "Used by" tab listing agents that depend on this skill
  • Edit the draft (/skills/:scope/:name/edit), then publish a new version via the Create Version modal (forward-only semver, SHA-256 integrity computed server-side)
  • Fork a system skill to create a local, mutable copy
  • Install / uninstall a skill on the current application with a single toggle
  • Yank a published version (marks it as withdrawn without deleting history)

Package IDs are scoped names: @appstrate/my-skill. There is no sk_ prefix. System skills live under @appstrate/… and are read-only (fork them to customize).

Pure SKILL.md skills

The minimal skill is a single file with YAML frontmatter and a Markdown body.

---
name: word-count
description: Counts the number of words in a given text. Use this skill when the user asks to count words or analyze text length.
---

# Word Count

Call this skill when you need to count words in a text input. Returns the count as a formatted string.

## Usage

Pass the text to analyze as a parameter. The skill returns a one-line summary.

Frontmatter fields (only these two are parsed):

FieldRequiredDescription
nameYesMachine identifier. Lowercase, alphanumeric, hyphens
descriptionYesUsed by the model to decide when to invoke the skill

The Markdown body is injected into the agent container at .pi/skills/{id}/SKILL.md at run time. The model reads it alongside its system prompt.

Skills with executable tools

A skill can also ship executable code that registers callable tools. This is the Pi Coding Agent SDK extension format (Appstrate-specific, not part of the Anthropic Skills standard). The entrypoint is a TypeScript file whose name is declared in the AFPS manifest's entrypoint field (commonly skill.ts for skills, {tool-name}.ts for tools).

// skill.ts
import { Type } from "@mariozechner/pi-ai";
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";

export default function (pi: ExtensionAPI) {
  pi.registerTool({
    name: "word_count",
    label: "Word Count",
    description: "Count the number of words in a given text.",
    parameters: Type.Object({
      text: Type.String({ description: "The text to count words in" }),
    }),
    async execute(_toolCallId, params) {
      const { text } = params as { text: string };
      const count = text.trim().split(/\s+/).filter(Boolean).length;
      return {
        content: [{ type: "text", text: `The text contains ${count} words.` }],
        details: { count },
      };
    },
  });
}

Gotchas:

  • Import Type from @mariozechner/pi-ai and ExtensionAPI from @mariozechner/pi-coding-agent. Both are pinned to ^0.67.2 in the current runtime.
  • The default export is a function that receives the pi API and calls pi.registerTool(...) imperatively — not an object literal.
  • parameters uses TypeBox (Type.Object, Type.String, Type.Union, …). JSON Schema is generated from the TypeBox schema.
  • execute signature is (_toolCallId, params). Return { content: [{ type: "text", text: "..." }], details?: {...} }.

A skill without executable code is 100% Anthropic-compatible. A skill that ships an extension requires an agent runtime that supports the Pi Coding Agent SDK.

Packaging (AFPS)

To publish a skill into Appstrate, package the skill folder as an AFPS ZIP. The AFPS spec adds a manifest.json at the root of the ZIP that declares the package type and version:

my-skill.afps (ZIP)
├── manifest.json       # type, name, version, description, integrity
├── SKILL.md            # YAML frontmatter + markdown body
└── skill.ts            # optional extension code

manifest.json minimum:

{
  "$schema": "https://afps.appstrate.dev/schema/v1/skill.schema.json",
  "type": "skill",
  "name": "@my-org/word-count",
  "version": "1.0.0",
  "description": "Counts the number of words in a given text."
}

name is a scoped identifier (@scope/name). Optional manifest fields include displayName, keywords, license, repository, and dependencies (for skills that depend on other skills/tools/providers). See the AFPS spec for the full schema.

Publishing

Publish a skill through the REST API. There is no dedicated CLI command today (see CLI Reference).

# First upload (creates the package)
curl -X POST http://localhost:3000/api/packages/skills \
  -H "Authorization: Bearer ask_your_key" \
  -H "X-App-Id: app_xxx" \
  -F "[email protected]"

# New version of an existing package (scoped id in the path)
curl -X POST http://localhost:3000/api/packages/skills/@my-org/word-count/versions \
  -H "Authorization: Bearer ask_your_key" \
  -H "X-App-Id: app_xxx" \
  -F "[email protected]"

Versions follow semver and are forward-only: you cannot publish a version lower than or equal to an existing one. Each upload is integrity-checked with an SHA-256 SRI hash (computeIntegrity) and served back on download via the X-Integrity header.

Using a skill in an agent

Skill attachment is manifest-only. An agent declares skill dependencies with semver ranges in its manifest.json:

{
  "type": "agent",
  "name": "@my-org/email-triage",
  "dependencies": {
    "skills": {
      "@appstrate/word-count": "^1.0.0"
    }
  }
}

At run time, Appstrate resolves each semver range, downloads the pinned version, and injects the SKILL.md into the agent container at .pi/skills/{id}/SKILL.md. If the skill ships an extension, its tools are registered with the agent's tool list automatically.

There is no dedicated PUT /api/agents/.../skills endpoint — to change an agent's skill dependencies, update its manifest and publish a new version.

System skills

Appstrate ships system skills in system-packages/ (loaded at boot with orgId: null). They are available to every organization without installation. Fork a system skill to create a local, mutable copy:

curl -X POST http://localhost:3000/api/packages/@appstrate/word-count/fork \
  -H "Authorization: Bearer ask_your_key" \
  -H "X-App-Id: app_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "name": "@my-org/word-count-custom" }'

Portability guarantee

  • A pure SKILL.md skill moves freely between Claude Code, the Claude Agent SDK, Appstrate, and any other Agent Skills-compliant host.
  • Only the AFPS wrapper is Appstrate-specific. Inside the ZIP, the skill folder is standard.
  • AFPS is published under CC-BY at github.com/appstrate/afps-spec.

Reference

On this page