Authoring APM Packages
A package is a named, independently versioned bundle of one or more primitives. Packages are how consumers depend on a curated set of primitives as a single unit (the way eslint-config-airbnb bundles ESLint rules).
Source of truth:
schemas/apm-package.schema.json.
When to create a package vs. a primitive
| Create a primitive | Create a package |
|---|---|
| You are shipping one self-contained capability. | You are shipping a curated set of primitives that is meaningful as a unit. |
| Consumers will install it on its own. | Consumers benefit from declaring one dependency instead of several. |
| Versioning is independent of any other content. | The set evolves together — a primitive bump is also a package bump (semver-wise). |
If a package only ever has one primitive, ship the primitive directly.
1. Pick a directory
packages/<name>/<name> is kebab-case (^[a-z][a-z0-9-]+$) and matches the name field in the manifest.
2. Write apm-package.json
Required fields:
| Field | Type | Notes |
|---|---|---|
id | string | Reverse-domain id matching ^[a-z][a-z0-9]*\.package\.[a-z][a-z0-9-]+$. Use calab.package.<name>. |
name | string | kebab-case, matches the directory. |
version | string | Semver MAJOR.MINOR.PATCH. Drives refs/tags/packages/<name>/v<version>. |
description | string | One-sentence catalog description. |
primitives | array | At least one. Each item is { "ref": "primitives/<type>/<name>" }. Order matters for human-readable output; uniqueness is enforced semantically. |
Optional fields:
| Field | Use it for |
|---|---|
maintainers | ["calab-ai"] or specific GitHub handles/teams. |
tags | Marketplace search. |
compatibility.tools | Subset of copilot, claude, codex, opencode, any. |
compatibility.min_catalog_version | Set when relying on newer catalog features. |
files | Optional [{ "src": "...", "dest": "..." }] for package-level files alongside the composed primitives. |
The GA calab-workspace-base package is the canonical example:
{
"$schema": "../../schemas/apm-package.schema.json",
"id": "calab.package.workspace-base",
"name": "calab-workspace-base",
"version": "1.0.0",
"description": "Base workspace package for Calab AI consumer repositories. Installs the core build and plan agents, shared coding-standards and security instructions, and essential CLI skills.",
"maintainers": ["calab-ai"],
"tags": ["workspace", "agents", "instructions", "skills", "base"],
"compatibility": {
"tools": ["copilot", "claude", "codex", "opencode", "any"]
},
"primitives": [
{ "ref": "primitives/agents/build" },
{ "ref": "primitives/agents/plan" },
{ "ref": "primitives/instructions/coding-standards" },
{ "ref": "primitives/instructions/security" },
{ "ref": "primitives/skills/az-cli" },
{ "ref": "primitives/skills/gh-cli" },
{ "ref": "primitives/skills/summarise" }
]
}The smaller calab-org-agents package shows the minimal shape (two agents, no extra files):
{
"$schema": "../../schemas/apm-package.schema.json",
"id": "calab.package.org-agents",
"name": "calab-org-agents",
"version": "1.0.0",
"description": "Org-level Copilot agent package. Composes the build and plan agents for distribution to calab-ai/.github-private via the APM release pipeline.",
"maintainers": ["calab-ai"],
"tags": ["agents", "org", "copilot", "github-private"],
"compatibility": { "tools": ["copilot"] },
"primitives": [
{ "ref": "primitives/agents/build" },
{ "ref": "primitives/agents/plan" }
]
}Composition rules
The schema and the publish pipeline together enforce these rules — break them at your peril.
- Refs are repository-relative paths, not ids. They match
^primitives/(agents|instructions|prompts|skills|hooks|init)/[a-z][a-z0-9-]+$. The composed primitive must already exist in the same registry release. - Each ref is unique within a package. JSON Schema only catches identical objects; the publish validator catches duplicate
refvalues. - Optional primitives (
{ "ref": "...", "optional": true }) are included if present at install time but do not block resolution if missing — use sparingly, only when a primitive is platform-conditional. - Package-level
filescopy additional assets next to the composed primitives.srcis package-relative;destis consumer-install-target relative. Both follow the samerelative-pathrules as primitives (no leading slash, no.., no backslashes). - Compatibility narrows.
compatibility.toolson a package should be the intersection of the tools each composed primitive supports. The catalog generator currently warns on mismatch; the publish step will reject it once strict mode lands.
Versioning
Packages follow strict semver:
- PATCH — no behavior change, no primitive set change (e.g. description tweak).
- MINOR — additive primitive (new ref) or non-breaking metadata change.
- MAJOR — primitive removed, primitive replaced with a different one, or any composed primitive made a major-version jump that breaks consumers.
A primitive’s minor/patch bump does not require a package bump unless the package explicitly pins to a specific primitive version (which today’s schema does not support — primitives are always pulled at the catalog version).
Validate locally
The primitive validator is live today:
pip install 'jsonschema==4.*'
python scripts/validate_primitive_manifests.pyA dedicated package equivalent (scripts/validate_package_manifests.py) is not yet shipped in calab-ai/apm-registry — it is planned and will follow the same pattern (jsonschema==4.* against apm-package.schema.json). In the meantime, package manifests are validated in CI as part of the catalog generation step on PR merge; for local pre-flight, validate by hand against the schema with any JSON Schema tool, or wait for the script to land.
Ready to release?
Continue to Publishing.
Related
- Concepts
- Authoring primitives
- Existing packages:
packages/ - Marketplace pages:
calab-workspace-base,calab-org-agents