Chapter 10

Becoming a Producer

Package and publish reusable APM primitives so other teams can consume them through the normal install loop.

Objective

After this chapter you can take agent context that already works in one repo — a prompt, an instruction set, a skill — and extract it into a standalone, versioned APM package that other teams install through the same Chapter 5 apm install loop, instead of copying the files by hand. You will see why copy-paste reuse quietly recreates Chapter 1's drift, author a producer apm.yml, snapshot the package with apm pack, and read the crucial producer/consumer symmetry: producing adds no fifth property — it makes you the source of Portability, Reproducibility, Provenance, and Governance for everyone downstream.

Concept/Theory

Reuse-by-copy is the Chapter 1 problem wearing a helpful face

Chapter 1 opened on drift: three copies of a review prompt in three home directories, each checking a slightly different threat model. Chapters 4–9 fixed that inside one repo — a manifest, a lockfile, a policy gate. But the moment a second team wants your good prompt, the old failure is one paste away. Copy the checkout-review prompt into another repo and the two copies begin to diverge immediately: no shared version, no lockfile, no review trail, no way to answer “which prompt produced this behaviour?” The uncomfortable truth this chapter names is that reuse-by-copy recreates the exact drift the book set out to kill.

Producing is the fix. It is the act of extracting context that already works — a prompt, a skill, an instruction set — into a standalone, versioned APM package that other repos install through the normal apm install loop, rather than copying files by hand. A copy is a point-in-time snapshot that starts drifting the instant it is made; a dependency is a pinned, hashed reference that a lockfile can reproduce and apm update can advance deliberately (Chapter 7). Producing converts copies into dependencies. APM frames the whole ramp around one promise: “there is no separate ‘build pipeline’ — the CLI is the build pipeline” (Producer overview).

A package is just a directory with apm.yml + .apm/

Here is the part that removes the intimidation. There is no packaging tool, no build config, no artifact type you have not already met. “An APM package is a directory with two things: an apm.yml manifest and a .apm/ source tree. Everything else … is generated, optional, or both” (package anatomy). The producer mental model is identical: “a producer package is just a directory with apm.yml.apm/README.md” (Producer overview).

You already know this shape from both sides. In Chapter 3 and Chapter 4 you authored primitives under .apm/instructions/ and .apm/prompts/; in Chapter 5 you consumed other people's packages that had exactly those directories. Producing is not new authoring — it is relocation into a package shape. The same source-vs-build split from Chapter 3 is now your responsibility: .apm/ is what you write and commit; .github/, .claude/, .cursor/, and apm.lock.yaml are generated. “Edit the source under .apm/ and re-run apm install — never edit the deployed copy” (package anatomy).

The symmetry: everything you consumed, you now become the source of

Producing does not add a fifth property to the book's four. It flips your role. Every benefit the earlier chapters named as a consumer win, the reader now supplies to downstream teams. When Meridian ships meridian-standards, its consumers get:

  • Portability — one manifest compiles the same primitives onto Copilot, Claude Code, and Cursor (Chapter 5);
  • Reproducibility — a lockfile that pins each dependency by resolved_commit and fingerprints its source tree by content_hash, so a bare apm install restores the same bytes; and, when a package is packed as a bundle, an extra per-file SHA-256 map on top (Chapter 6);
  • Provenance — a pinned, hashed, reviewable source that passes the same install-time scanners from Chapter 8;
  • Governance — the package installs through the same apm-policy.yml gate from Chapter 9; a policy can even require it, exactly as Meridian's require: meridian-finance/meridian-standards rule did.

This is the spine sentence of the whole book landing: the reader stops being an npm user and becomes an npm publisher — the same manifest, lockfile, and policy discipline, now pointed outward. APM states the symmetry directly: “every package you publish here installs through the [consumer ramp's] apm install command” (Producer overview). The official ramp is a five-rung ladder — author → compile → preview → pack → publish — and this chapter walks the two rungs that matter for internal reuse: author the package, and pack it, then closes the loop by installing the result back into a scratch repo. “You don't need a marketplace to start” (Producer overview).

In APM

A producer package is a directory you already know how to read

Here is meridian-standards as a plain directory. Nothing is scaffolded by a special tool: an apm.yml, a README.md, and one subdirectory per primitive type under .apm/. Read it and you will recognise every file from earlier chapters.

The authored meridian-standards tree — manifest, README, and three primitives under .apm/<type>/. plugin.json is not here: it is generated by apm pack, not hand-written. apm v0.23.1
meridian-standards/
├── apm.yml                                      # producer manifest (identity + distribution metadata)
├── README.md                                    # what the package is and how to install it
└── .apm/                                        # the SOURCE tree — one directory per primitive type
    ├── instructions/
    │   └── meridian-engineering.instructions.md # shared engineering rules (Money type, idempotency…)
    ├── prompts/
    │   └── checkout-review.prompt.md            # the checkout-review prompt, now reusable
    └── skills/
        └── secure-payment-review/
            └── SKILL.md                          # PCI / payment-review skill

Two precisions keep you out of trouble later. First, author under .apm/<type>/, not root convention directories: apm pack is liberal and will also collect a root skills/ or instructions/ folder, but apm install is stricter and may not discover those on the consumer side — so a file that shows up in the bundle can be silently skipped on install, “silently removing those guardrails from consumer environments” (pack a bundle). Second, commands ship as prompts — there is no .apm/commands/ directory; a slash-command is authored as a .apm/prompts/*.prompt.md (author primitives).

The producer manifest: identity + type + distribution metadata

A consumer manifest answers “what does my repo need?” A producer manifest also answers “who is this package, where does it come from, and what is it for?” so other people can evaluate and discover it. It is the same apm.yml schema you wrote in Chapter 4 — producers simply fill in the optional distribution fields a private consumer manifest can leave out. Here is Meridian's:

backend/examples/ch10/meridian-standards/apm.yml — the same schema as a consumer manifest, with the distribution fields filled in. Verified against a fresh apm init template + the synthesised plugin.json. apm v0.23.1
name: meridian-standards          # REQUIRED — package identity (its slug when others depend on it)
version: 1.0.0                    # REQUIRED — semver
description: Shared Meridian engineering instructions, prompts, and review skills.
author: Meridian Platform Engineering   # string -> { name: "…" } in the generated plugin.json
license: MIT                      # SPDX id — recorded in the lockfile & SBOM (omitting it -> NOASSERTION)
repository: https://github.com/meridian-finance/meridian-standards
keywords: [meridian, checkout, fintech, ai-agents]
type: hybrid                      # REAL enum, but RESERVED/INERT at v0.23.1 — changes no behaviour today
targets:                          # incl. copilot/claude -> routes `apm pack` to plugin.json mode
  - copilot
  - claude
  - cursor
includes: auto                    # consent to publish everything under .apm/ (the `apm init` default)

Only two fields are required to parse — name and version. Everything else is optional, and unknown top-level keys are preserved but ignored (manifest schema §2). The distribution fields are the package's business card: repository gives provenance, license sets reuse terms, keywords feed discovery, and author records ownership.

Which keys apm accepts in a producer manifest, and what each one does for a shared package. Verdicts checked against apm init, the synthesised plugin.json, and the SBOM warnings. apm v0.23.1
Key Verdict What it does for a shared package
name required Package identity — the slug other repos depend on.
version required (semver) The version consumers pin. Meridian ships 1.0.0.
description optional, read One-line summary; flows into plugin.json; apm pack warns if missing.
author optional, read Attribution. A string becomes { "name": … } in plugin.json.
license optional, read SPDX id. Recorded in the lockfile and SBOM; omitting it records NOASSERTION.
repository optional, read Provenance; mapped into plugin.json.
keywords optional, read Discovery tags; mapped into plugin.json (and marketplace tags).
targets optional, validated Harnesses to compile for. Unknown target values are a parse error; also routes apm pack.
includes: auto optional (the default) Consent to publish everything under .apm/.
type real enum, but inert instructions | skill | hybrid | prompts. Reserved for future overrides; no effect at v0.23.1.
(unknown key) silently preserved No error, no warning — kept in the file, ignored by the CLI.

The metadata is not decorative: it becomes the package's identity card on the other end. When you do not author a plugin.json yourself, apm pack synthesises one from apm.yml — so apm.yml stays the single source of truth (pack a bundle):

The plugin.json that apm pack generates for meridian-standards — mapped straight from the manifest. Note type is absent. apm v0.23.1
{
  "name": "meridian-standards",
  "version": "1.0.0",
  "description": "Shared Meridian engineering instructions, prompts, and review skills.",
  "author": { "name": "Meridian Platform Engineering" },
  "license": "MIT",
  "repository": "https://github.com/meridian-finance/meridian-standards",
  "keywords": ["meridian", "checkout", "fintech", "ai-agents"]
}

apm pack — snapshot the package into a distributable artifact

apm pack is the pack rung of the ladder: it turns your authored primitives into something you can hand off. The one rule to internalise before you run it: apm pack packs the files your last apm install deployed — not the raw .apm/ tree. So you always apm install first. “If apm pack reports ‘No deployed files found’, your apm.lock.yaml has no deployed_files entries. Run apm install first” (pack a bundle).

What apm pack produces is decided by which blocks your apm.yml declares. This routing table is the thing to memorise — it explains why the same command behaves very differently across packages:

apm pack routing, decided by manifest content (confirmed via the “Nothing to pack” error). apm v0.23.1
If apm.yml contains… apm pack writes… Where
a dependencies: block a bundle (plugin.json + primitive dirs + embedded lockfile) ./build/<pkg>/, or a .zip with --archive
a marketplace: block marketplace artifact(s) .claude-plugin/marketplace.json
targets: including claude or copilot plugin.json manifest(s), no bundle in-tree: .github/plugin/, .claude-plugin/
none of the above “Nothing to pack” exit 1

meridian-standards has targets: (including copilot and claude) but no dependencies: block, so it lands in row 3 — plugin.json mode, not a bundle. The safe pre-flight is apm pack --dry-run --verbose: it reports exactly what would be written and touches nothing on disk. It runs entirely offline:

Install first, then dry-run the pack. For meridian-standards that is plugin.json mode: two manifests would be written, no bundle. Both commands are offline, exit 0. apm v0.23.1
$ apm install                     # STEP 1 — pack packs what install DEPLOYS, so install first
  [i] Targets: claude, copilot, cursor  (source: apm.yml)
    [+] <project root> (local)
    |-- 1 prompts integrated -> .github/prompts/
    |-- 2 commands integrated -> .claude/commands/, .cursor/commands/
    |-- 3 rule(s) integrated -> 3 targets
    |-- 1 skill(s) integrated -> .agents/skills/, .claude/skills/
  [*] Installed 1 APM dependency in 1.8s.                        # exit 0

$ apm pack --dry-run --verbose    # STEP 2 — pre-flight: what WOULD pack write? (writes nothing)
  Would write plugin manifest to  .github/plugin/plugin.json     # copilot target
  Would write plugin manifest to  .claude-plugin/plugin.json     # claude target
  # cursor is in targets: but gets NO plugin.json — the plugin contract is claude/copilot only

Scaffolds: apm plugin init and apm marketplace init

You do not have to hand-write the shape. apm plugin init scaffolds a fresh producer package — it writes an apm.yml plus a plugin.json, and adds a devDependencies: block (dev-only dependencies that are excluded from what apm pack ships — the right home for test-only tooling). It is fully offline:

apm plugin init scaffolds rung 1 of the ladder — a valid producer skeleton, no network. apm v0.23.1
$ apm plugin init -y --target copilot     # scaffold a brand-new producer package (offline)
  [+] Created apm.yml                     # standard template + a devDependencies: block
  [+] Created plugin.json                 # exit 0

apm marketplace init is the discovery side. It adds a marketplace: block to apm.yml; apm pack then builds a .claude-plugin/marketplace.json that consumers register with apm marketplace add <owner>/<repo>. A marketplace is a discovery index over git, not a hosted registry — “a curated index of packages that one repo publishes and many repos install from” (publish to a marketplace). One trap worth knowing: unknown keys inside the marketplace: block are rejected at parse time — stricter than the top-level manifest, which silently ignores unknowns.

apm marketplace init scaffolds a marketplace: block (offline). Consumers later reach it with apm marketplace add. apm v0.23.1
$ apm marketplace init --name meridian-marketplace --owner meridian-finance   # offline, exit 0

# appended to apm.yml:
marketplace:
  owner: { name: meridian-finance, url: https://github.com/meridian-finance }
  build: { tagPattern: "v{version}" }
  outputs: { claude: {} }          # codex optional; each output writes its profile-default path
  packages:
    - name: example-package        # placeholder the scaffold writes; edit to meridian-standards
      description: Human-readable description of the package
      source: meridian-finance/example-package
      version: "^1.0.0"

apm publish — the registry path (experimental)

APM also has an apm publish verb that uploads to a REST registry — but it is gated behind an experimental feature that is disabled by default, and publishing itself pushes over the network. For this book it is SKIPPED-needs-network. The gate check is verifiable offline, and it tells you exactly what is going on:

apm publish is gated behind the disabled registries feature. The gate is verified offline; the actual upload is SKIPPED-needs-network. apm v0.23.1
$ apm publish --package meridian-finance/meridian-standards --dry-run
  Error: apm publish requires the experimental registries feature.
  Enable with: apm experimental enable registries.              # disabled by default

Even once enabled, publish uploads via PUT /v1/packages/{owner}/{repo}/versions/{version} — a network operation against a registry API that is still a v0.2 working draft. Most teams never need it: a git ref is the zero-infrastructure distribution channel, and it is enough for everything this chapter does. The registry route belongs to the enterprise story in Chapter 11.

When to use / pitfalls

How to hand a package off

Producing the package is one thing; getting it to consumers is another. APM distribution is git-based today — there is no central public registry — so pick the channel by reach, not habit. For internal reuse, a pinned git ref is almost always the right answer.

Choosing a distribution channel for a produced package.
Channel What it is Reach for when…
git ref (owner/repo#<ref>) The primary APM channel — zero infrastructure, pinned + hashed by the lockfile. the consumer can reach your git host. Default choice for internal reuse.
bundle (apm pack --archive) A .zip whose files are pinned by SHA-256; needs a dependencies: closure with remote refs. you must move it by hand — offline, air-gapped, or a customer hand-off.
marketplace A discovery index over git (apm marketplace add), not a hosted registry. many repos need to discover many curated packages.
registry (apm publish) Semver-range installs from a REST service — experimental (registries). your org runs a registry proxy (Chapter 11).

Worked example

The producer → consumer round-trip

The real test of a produced package is not that it packs — it is that it stands alone. Local primitives can lean on paths, tools, or context that only exist in the origin repo, so the producer's discipline is to install the package into a fresh, throwaway repo and prove it works there. “Install your own package in a scratch repo before declaring it shipped” (Producer overview). This is the consumer ramp from Chapter 5, seen from the producer's chair.

You can do it fully offline with a local-path source install. From a fresh scratch repo, install meridian-standards by path: APM resolves its .apm/ source primitives (ignoring the producer's own compiled output), deploys them to the scratch repo's target, and caches the package — exactly as it would for any git-sourced dependency, with no special casing:

The offline round-trip: install meridian-standards into a throwaway repo by local path. The extracted context stands alone — exit 0, no network. apm v0.23.1
$ apm init -y --target copilot          # a scratch consumer repo, outside meridian-standards
$ apm install ../meridian-standards     # local-path SOURCE install — fully offline
  [*] Validating 1 package...
  [+] …/meridian-standards
  [>] Resolving …/meridian-standards...
    |-- 1 prompts integrated       -> .github/prompts/
    |-- 1 instruction(s) integrated -> .github/instructions/
    |-- 1 skill(s) integrated       -> .agents/skills/
  [*] Installed 1 APM dependency in 1.4s.        # exit 0 — the extracted context stands alone

One caveat about that local-path install: APM records the dependency as a machine-specific absolute path, which is not committable. For a portable manifest you use a relative ./ path (dev-only) or — the real story — a git ref. That is how the loop actually closes: Meridian pushes meridian-standards to its private host, tags v1.0.0, and meridian-checkout depends on it. Because that host is private, the resolve is SKIPPED-needs-network in this book — but the manifest change is the whole point:

meridian-checkout/apm.yml closing the loop — the extracted conventions return as a pinned dependency, and the version graduates to 1.0.0. Resolving the private repo is SKIPPED-needs-network. needs network apm v0.23.1
# SKIPPED-needs-network: meridian-finance/meridian-standards is a private repo
name: meridian-checkout-agent-context
version: 1.0.0                    # <- the beat: the checkout context graduates from v0.x to 1.0.0
author: Meridian Checkout Team
targets: [copilot, claude, cursor]
dependencies:
  apm:
    - meridian-finance/meridian-standards#v1.0.0   # the extracted conventions, back in as a governed dep
  mcp: []
includes: auto

That single pinned line is the payoff of the whole book. The checkout team no longer copies its own conventions between services — it depends on them. The lockfile pins the exact commit and content hash (Chapter 6); the install-time scanners vet the source (Chapter 8); and the org policy from Chapter 9 can now require: meridian-finance/meridian-standards, so every Meridian service restores the same approved conventions, byte-for-byte. The four properties the reader consumed for nine chapters are now the four properties they produce.

Recap & next

Recap

  • Reuse-by-copy recreates Chapter 1's drift. Producing turns a working convention into one versioned, reviewable package that consumers restore identically — a dependency, not a fork.
  • A package is just a directory with an apm.yml and a .apm/<type>/ source tree. There is no separate build pipeline; the CLI is the build pipeline. Producing is relocation into a package shape, not new authoring.
  • A producer manifest is the same schema as a consumer's, with the distribution fields filled in (description, author, license, repository, keywords). Only name + version are required; type is a real enum but inert at v0.23.1; unknown keys are silently preserved.
  • apm pack routes by manifest content: dependencies: → a bundle in ./build; marketplace: → a marketplace index; targets: (claude/copilot) → plugin.json in-tree; nothing → “Nothing to pack.” meridian-standards is plugin.json mode.
  • Mind the corrections: run apm install before apm pack (pack packs deployed files); -o dist does not work — the default output is ./build, and -o/--archive only apply in bundle mode.
  • Distribution is git-based: a git ref is the zero-infra default; bundles are for offline hand-off; a marketplace is a discovery index, not a registry; and apm publish (experimental registries) is SKIPPED-needs-network.
  • Meridian extracted its checkout-review prompt, engineering instructions, and secure-payment skill into meridian-standards, proved it stands alone with an offline round-trip, and closed the loop by depending on meridian-finance/meridian-standards#v1.0.0 — graduating meridian-checkout to 1.0.0.

Next

Meridian now produces a package its own teams consume — but a package that many teams must adopt raises new questions: how do you gate it in CI, serve it in an air-gapped environment, and govern its use across a whole organisation without turning developer setup into a bottleneck? Chapter 11 — Enterprise at Fleet Scale takes the producer and consumer ramps to org scale: apm audit --ci gating, a registry proxy, air-gapped installs, and the adoption playbook that makes the Chapter 9 policy stick.