Morphic Agent Framework

Shipping A Plugin

Packaging skills, agents, commands and definitions as a distributable Claude Code plugin.

A Claude Code plugin is a directory of Markdown files with YAML frontmatter, plus a JSON manifest.

Writing them by hand works right up until two agents need the same paragraph of instructions, at which point you are maintaining copies. You know the rest of that story — it is the first page of this section.

psy init my-plugin --template claude-plugin
cd my-plugin
psy check
psy build

The layout

my-plugin/
  psy.config.ts
  src/
    framework.psy          your local vocabulary, if you want one
    skills/
    agents/
    commands/
    concepts/              what the plugin's users are building
    references/
    index.psy
  plugin/                  generated
    .claude-plugin/plugin.json
    agents/*.md
    skills/*/SKILL.md
    commands/*.md

The only difference from a project build

One option.

import { defineConfig } from "@psy/config";
import { morphic } from "@psy/morphic";
import { claude } from "@psy/adapter-claude";

export default defineConfig({
    source: ["./src"],

    frameworks: [morphic()],

    targets: [
        claude({
            output: "plugin",
            plugin: {
                name: "my-plugin",
                description: "Agents, skills and commands defined in Psy.",
                version: "0.1.0",
                license: "MIT",
            },
        }),
    ],
});

Omit plugin and you get a .claude/ directory for one project. Include it and you get the plugin layout plus the manifest:

{
  "name": "my-plugin",
  "description": "Agents, skills and commands defined in Psy.",
  "version": "0.1.0",
  "license": "MIT"
}

Everything else — how a skill renders, what becomes a section, which frontmatter field when lands in — is identical to What Gets Generated.

What you get

psy build
plugin/.claude-plugin/plugin.json
plugin/agents/build-feature.md
plugin/agents/build-project.md
plugin/commands/review.md
plugin/skills/implement-change/SKILL.md
plugin/skills/project-context/SKILL.md
plugin/psy-generated.md

A generated command, in full:

---
description: Review the current change.
argument-hint: "[path]"
allowed-tools: "Read, Grep, Bash(git diff:*)"
---

<!-- Generated by Psy from src/commands/Review.psy. Do not edit by hand; edit the .psy source and run `psy build`. -->

Review the change at the given path, or the current diff when no path is
given.

## Skills

- `project-context` (ProjectContext)

Ship the definitions too

This is the part that is easy to miss, and it is most of the value of building a plugin in Psy rather than by hand.

A plugin is usually for something — a framework, a platform, a house style. Its skills are only as good as the reader's understanding of that thing, and you cannot assume the reader has one.

So put it in the plugin:

export concept OurStack:
    description: How applications on our platform are put together.

    overview:
        Every service is a single deployable that owns its data. Services talk
        over the event bus, never by calling each other directly.

export reference ErrorStyle:
    description: How errors are surfaced to users.

    guidance:
        Say what happened, then what to do about it. Never show a stack trace
        to an end user.
export skill ImplementService:
    description: Implement a change in a platform service.
    when: Use when changing anything inside a service.

    context:
        - OurStack
        - ErrorStyle

    prompt:
        Keep cross-service calls on the event bus.
---
name: our-stack
description: "Concept: How applications on our platform are put together."
---

# OurStack

## Overview

Every service is a single deployable that owns its data. Services talk over the
event bus, never by calling each other directly.

## Authority

This describes the product as it is meant to work. Where the implementation
disagrees, treat this as correct unless the task is explicitly to change it.
---
name: implement-service
description: Implement a change in a platform service. Use when changing anything inside a service.
---

# ImplementService

Keep cross-service calls on the event bus.

## Context

Consult these definitions of how the product works:

- `our-stack` (OurStack)
- `error-style` (ErrorStyle)

Those definitions ship as skills alongside the working ones, and get loaded when relevant. Anyone installing your plugin gets the instructions and the model of the thing the instructions are about.

Specifying Software is the whole chapter on writing these well.

Scoping one build from many

If a repository produces more than one plugin, or a plugin plus a project-local .claude/, scope each target with modules:

targets: [
    claude({
        output: "plugin",
        modules: ["src/plugin/"],
        plugin: { name: "my-plugin", version: "0.1.0" },
    }),
    claude({
        output: ".claude",
        modules: ["src/internal/"],
    }),
],

Without modules, every target emits every declaration in the program, and you will ship your internal agents to strangers.

Keeping it honest

psy build --check

fails when any generated file is missing, out of date, or no longer produced. Wire it into CI next to your tests. Generation is deterministic, so a clean --check means the committed plugin matches the Psy sources exactly.

Renaming a declaration removes its old artifact, provided the build can prove it wrote the file — see pruning. For a plugin you commit and publish, turn artifacts.manifest on; a stale skill left behind after a rename is a file your users will load.

Names

A spec's PascalCase name becomes a kebab-case artifact name:

ReadPsySpec       -> read-psy-spec
BuildCLI          -> build-cli
IdentifySpecGaps  -> identify-spec-gaps

Two declarations that would collide on the same slug are a framework error, not a silently overwritten file — and the check spans skills, agents, commands and all five definition kinds at once.

This is how Psy maintains itself

The same mechanism produces the agents that maintain the Psy compiler. build/ in the Psy repository declares BuildPsy, ReviewPsy, DocumentPsy and the specialised build agents, and psy build writes them into .claude/.

See Psy Describes Psy.

Next: Tutorial: A Paint App

On this page