Chapter 4

The Manifest: apm.yml

Author a valid apm.yml that declares Meridian's first shared agent-context dependencies.

Objective

After this chapter you can author a valid apm.yml — the first real APM file you write — that declares a project's identity, the harnesses it targets, and one locally-authored instruction, and explain why that single committed file is what turns Chapter 1's scattered, per-machine setup into portable, reviewable, version-controlled state.

Edition 1.2 targets APM 0.31.0. The original scaffold and Meridian recording remain labeled 0.23.1. The current local fixture keeps the same small setup: one checkout instruction, shared across three targets.

Concept/Theory

A manifest is “what this project needs,” written down

Before a manifest, a project's agent context is whatever files happen to sit on each machine — the exact drift Chapter 1 diagnosed at Meridian: a copilot-instructions.md here, a private review.prompt.md there, Cursor rules no one else can see. Nothing declares what the project needs; each laptop is its own source of truth, so the setups quietly diverge. A manifest replaces “whatever is on my disk” with “what this repository declares” — one version-controlled file that travels with the code. This is where portability stops being a slogan and becomes a file you can open in a pull request.

In APM that file is apm.yml, and the docs describe it as a contract: it “declares the full closure of agent primitive dependencies, MCP servers, scripts, compilation settings … for a project” and is “the contract between package authors, runtimes, and integrators” (manifest schema). Only two fields are required to parse — name and version — and everything else is optional (manifest schema §2). The examples here use APM's current working-draft shape, without an explicit schema selector; the init template is a starting point, not the complete set of allowed fields (APM 0.31.0 manifest reference).

Chapter 1 ended with a sketch of Meridian's apm.yml and called it “a promise, not yet a program.” This chapter makes it real: the same shape, now a file the tool actually reads. If you have ever opened a package.json, you already know how to read it — “the shape mirrors package.json on purpose: name, version, dependencies… scripts” (package anatomy).

What the manifest is, and what it is not

Two distinctions keep the rest of the book straight. First, a manifest is not the per-machine files it replaces. Those were many hand-edited outputs; the manifest is one authored input, committed to the repo, that those outputs are generated from. You edit the declaration and source — not the APM-managed copies under .github/, .claude/, or .cursor/. Those directories can also contain human-owned files; their location alone does not make every file generated output.

Second, a manifest is not a lockfile. The manifest is human-authored declared intent — what you want, which may include a moving branch or a version range. The lockfile (apm.lock.yaml, Chapter 6) is the machine-generated resolved state — the exact commits and content hashes you actually got. The manifest says what; the lockfile records exactly which (package anatomy). This chapter is entirely about the first file.

The manifest is the Portability surface — its whole job — but it also seeds the other three properties without yet delivering them: it is the input the lockfile resolves (Reproducibility, Chapter 6), the surface a policy inspects (Governance, Chapter 9), and the declaration that makes provenance reviewable (Provenance / security, Chapter 8). Get the declaration right here and the other three chapters have something honest to work with.

A declaration also sets an authority boundary: eligible package manifests and component declarations determine what can be deployed; directory conventions supply defaults where the relevant declaration is absent. Selected targets define the destination set, and package or per-dependency restrictions can narrow it, not silently add harnesses. That is reviewable deployment intent, not a guarantee that an agent obeys a rule or runs in a sandbox (package authority; Chapter 8's runtime boundary).

In APM

What apm init writes, key by key

You do not start from a blank file. Plain apm init --yes in an empty directory creates only apm.yml — no .apm/ source tree, lockfile, or harness deployment. You then edit its sensible defaults (APM 0.31.0 init reference). The historical scaffold below retains its example identity and annotations:

Historical scaffold shape for acme-agents: name from the directory, author from Git configuration, commented targets, and includes: auto. These defaults remain in 0.31.0. license is absent from plain init, not forbidden by the schema. apm v0.23.1
# apm.yml -- one file, sensible defaults; you edit it from here
name: acme-agents
version: 1.0.0
description: APM project for acme-agents
author: Ravi Sharma            # auto-detected from your git user.name
# Which agent platforms to deploy to (uncomment to pin):
# targets:
#   - copilot
#   - claude

dependencies:
  apm: []
  mcp: []
includes: auto
scripts: {}

Already have native-layout skills? Use Chapter 5's discovery/apply workflow: apm init --discover inventories admissible local packages; consented apply merges their references without moving or translating the sources. Installation is a later step. Plain init --yes can overwrite an authored manifest; it is not the merge shortcut.

Every key here is a question the team used to answer per-machine, now answered in one reviewable place. The point is the meaning and shape of each field, not an exhaustive flag tour:

Plain-init keys, with their current types and meaning — not an exhaustive schema. Field set checked with empty-directory init and the current local fixture. apm v0.31.0
Key Type What it declares Carries
name string The package's declared identity. Init defaults to the directory name; a dependency's source coordinates are a separate choice. Portability
version string (semver) This package's own version. Meridian starts pre-1.0 at 0.1.0 and advances one beat per chapter. Portability
description string A one-line human summary shown in listings. Replace the boilerplate. Portability
author string Optional attribution. apm init auto-fills it from your git user.name; set it to the team or org for a shared repo. Portability
targets list (commented by default) The selected harness destinations. If absent, a CLI selection, saved default, or filesystem detection decides. Pin the list for a shared repo. Portability
dependencies.apm list of strings or objects Package references: Git, local paths, or experimental registry sources. Empty is valid. Source forms appear below; installs follow in Chapter 5. seeds Reproducibility
dependencies.mcp list of strings or objects MCP registry references or server objects, not a name→map. Connection trust is developed in Chapter 8, policy in Chapter 9. seeds Security / Governance
includes auto | list Consent for local content from the selected source layout. An explicit list is not an install filter, but is exhaustive for plugin packing; see the operation-specific pitfall. Portability
scripts map Named shell commands (name: "command") run with experimental apm run. Empty for now; used in Chapter 5. Portability

Two of those keys read straight out of Chapter 3. targets is the target ≠ harness distinction, now written down: you declare the targets, and the harness still runs independently. Pinning them in the manifest makes the destination choice repeatable for developers and CI, rather than silently tracking whichever tool folders happen to exist on the last person's machine (manifest schema §3.6). The target-selection rules below explain overrides and bootstrap. And includes: auto records consent for the local primitives discussed next.

Meridian authors in .apm/; installed copies go in apm_modules/

A manifest brings your own sources and referenced packages into the same deployment workflow. Meridian's locally-authored primitives live under the .apm/ source tree (instructions/, prompts/, skills/, agents/, hooks/) — “the conventional source root for APM packages.” Referenced packages are declared under dependencies.apm and materialized at install time into the generated, gitignored apm_modules/. They can come from Git, local paths, or an enabled registry. You edit your source; you never hand-edit apm_modules/ (package anatomy).

.apm/ is Meridian's chosen convention, not the only supported layout. Skills and plugins can retain their native source trees. An eligible apm.yml — accompanied by .apm/ or APM/MCP dependencies — takes precedence over a co-located plugin marker; metadata-only apm.yml does not force that route. Root skills and skill collections are separate supported forms (package layouts and precedence).

A root SKILL.md beside apm.yml is a supported hybrid layout. Do not confuse that with setting type: hybrid: the top-level type field remains reserved for future overrides, not an active switch for selecting primitive types (reserved type field).

For Meridian's layout, includes: auto explicitly consents to local content. With no remote dependencies, apm install treats this repo as the source package and projects its eligible .apm/ primitives to the selected targets (your first package). That is precisely Meridian's first manifest: one valid local instruction, zero remote dependencies. Consuming other people's packages (Chapter 5) is an addition to local authoring, never a precondition for it.

The two dependency buckets — shape now, installs in Chapter 5

Meridian declares no dependencies at v0.1.0, but you should recognize the two buckets used here: dependencies.apm references packages, while dependencies.mcp declares tool connections. Both accept lists of strings or typed objects. The following are separate fixture examples, not additions to Meridian's first manifest.

Git declaration from the current core/pinned-skill fixture: one public skill at a full commit, not the whole sample package. Needs public Git access on a cold cache. Fixture independently checked on 2026-09-16. apm v0.31.0
dependencies:
  apm:
    - git: microsoft/apm-sample-package
      ref: fb2851683be0e0e7711421d518bd8dba23b0b1f6
      path: .apm/skills/style-checker
  mcp: []

The source identity implements Chapter 2's pinning lesson. In a Git object, use ref, not registry-style version; unknown object keys are rejected. path selects content inside that repository. Git objects also accept explicit clone URLs for other Git servers. A tag such as #v1.0.0 is readable but can be moved; the full commit above fixes the source revision, and the lockfile records the resolved graph (Git dependency forms).

Without git, a path: ./pkg object refers to a local package. alias gives it a local resolution name; skills selects from the skills it actually declares, and dependency targets intersects the install's target set. This is the authority boundary in concrete form:

Local-path selection fixture, including its own pkg/ source: the project selects Copilot and Claude, but this dependency reaches only Claude, with only kept. Observed selected-skill deployment, not a plugin runtime test. Its CI audit exits 1 because this legacy-plugin input has no pkg/apm.yml; the successful selection is not an audit-clean guarantee. apm v0.31.0
# An explicit plugin skills declaration replaces directory discovery.
name: legacy-consumer
version: 1.0.0
targets: [copilot, claude]
dependencies:
  apm:
    - path: ./pkg
      alias: scoped-skills
      targets: [claude]
      skills: [kept]
includes: auto

That fixture's legacy plugin.json declares "skills": ["./skills/kept", "./extra/custom"]; an extra conventional skill directory is not permission to deploy it. An explicit "skills": [] means none, while omitting the key permits conventional discovery. Consumer selection cannot resurrect undeclared skills (declaration-first plugin intake).

Git remains the default path, not the only shipped source. APM 0.31.0 includes experimental REST registries, enabled explicitly with apm experimental enable registries. A named registry supplies the endpoint; its dependency object uses id and version, not Git's ref. The client and HTTP API exist; that does not establish a Microsoft-operated public searchable hub (registry guide).

Selected fields from the loopback registry fixture: fixture is a configured registry name, operations/exact its package ID, and 1.0.0 the selector. Requires the isolated test server and experimental registry feature; this is not a public endpoint or a standalone setup recipe. apm v0.31.0
registries:
  fixture:
    url: http://127.0.0.1:18431
  default: fixture
dependencies:
  apm:
    - registry: fixture
      id: operations/exact
      version: 1.0.0

A configured default registry can reroute plain owner/repo#selector shorthand. Explicit git: stays Git, so it makes the source choice unambiguous even on a machine with a registry default. Keep package registries distinct from MCP registries and Git proxies; distribution choices return in Chapter 10.

MCP entries describe connections, not ordinary primitive packages. The self-defined object below supplies a name, transport, executable, and arguments. The list also accepts MCP registry-reference strings; objects are not its only legal form (MCP dependency forms).

MCP object shape from a configuration-only fixture. The executable is deliberately nonexistent; no MCP server or harness was started. MCP identity lookup can require network access. Trust and runtime boundaries belong to Chapter 8. apm v0.31.0
dependencies:
  apm: []
  mcp:
    - name: operations-fixture
      registry: false
      transport: stdio
      command: operations-fixture-not-a-real-server
      args: [--fixture]

Reviewability includes the edit itself. The current APM dependency writer preserved the tested leading comment, blank line, and target-list style when adding a package. A direct MCP add uses a separate path that still reserialized the tested manifest and removed its comment. Review the diff after either operation; do not assume every writer preserves formatting (APM dependency writer; Chapter 5's add workflow).

When to use / pitfalls

Keep your first manifest minimal

The dividing line from Chapter 1 still holds: commit an apm.yml the moment context is shared and plural — multiple developers, multiple harnesses, onboarding that should be reproducible. For a spike you will throw away, a manifest is ceremony you do not need yet.

When you do write one, resist the urge to fill every key. A strong first manifest declares only three things: identity (name, version, a real description), targets (the harnesses your team actually uses), and the local primitives it authors under .apm/ (via includes: auto). Remote dependencies and scripts are real features — they are just Chapter 5's beat, added when you need them, not scaffolding to fill in up front. The empty dependencies buckets and scripts: {} that apm init leaves behind are valid empty declarations; there is no need to fill them yet.

A local path is not an immutable Git pin: its source must remain available with the project. Nor does apm install --frozen universally veto declaration changes. In 0.31.0, a newly declared local-path dependency can deploy and rewrite an existing lock; see Chapter 5's verified exception. Frozen's limited structural checks are not a runtime security guarantee or a content audit.

Worked example

Meridian's first real apm.yml

Here is the payoff. Meridian takes the apm init scaffold, sets its identity, uncomments and pins three targets, and authors one local instruction — the checkout-domain rule that drifted in Chapter 1 (“use the Money value object; treat retries as idempotent”). No remote dependencies, no scripts. The historical snapshot and current fixture are shown separately: the same declaration still expresses the same shared intent.

Historical exact chapter snapshot of Meridian's first manifest (v0.1.0): identity, three targets, and consent for local content. The original end-to-end apm install recording used no network. apm v0.23.1
name: meridian-checkout-agent-context
version: 0.1.0
description: Shared AI-agent context for the Meridian checkout service.
author: Meridian Checkout Team
targets:
  - copilot
  - claude
  - cursor

dependencies:
  apm: []
  mcp: []
includes: auto
scripts: {}
Current minimal fixture at backend/examples/updates/1.2/core/local-instruction/apm.yml. The same Meridian v0.1.0 declaration and instruction were independently installed, restored, frozen, and audited on 2026-09-16; no network or agent runtime. apm v0.31.0
# Materialize one local instruction in three native target formats.
name: meridian-checkout-agent-context
version: 0.1.0
description: Shared AI-agent context for the Meridian checkout service.
author: Meridian Checkout Team
targets:
  - copilot
  - claude
  - cursor

dependencies:
  apm: []
  mcp: []
includes: auto
scripts: {}

The instruction itself is authored once, under a recognized primitive directory. Install discovers it without path enumeration; includes: auto records consent. Its frontmatter carries a description and an applyTo glob — the same instruction shape from Chapter 3:

.apm/instructions/meridian-checkout.instructions.md — one source of truth for the checkout rule, authored by hand. The source is unchanged. apm v0.23.1; rechecked with v0.31.0
---
description: Meridian checkout service engineering rules
applyTo: "**/*.{ts,tsx,md}"
---

Use the Meridian `Money` value object for currency math. Treat checkout retries as
idempotent operations keyed by `paymentAttemptId`. Never suggest storing card data in
application logs.

With the manifest and the one source file in place, a single command restores the context. Running apm install with no package argument means “deploy from apm.yml”: APM treats the local project as one package and fans its instruction out to the declared targets.

Historical apm install recording: one local instruction, three targets, no network or fetched package. Its wording and timing are not a current-output promise. apm v0.23.1
$ apm install
  [i] Targets: claude, copilot, cursor  (source: apm.yml)
    [+] <project root> (local)
    |-- 3 rule(s) integrated -> 3 targets
  [i] Added apm_modules/ to .gitignore
  [*] Installed 1 APM dependency in 2.5s.
Run in the current local-instruction fixture directory with the manifest and source above. The same bare command deploys the three native files below; verified offline on 2026-09-16. apm v0.31.0
apm install

This is the concrete portability win. One source file, one command, and the same intent lands in three harnesses' native locations — different directories, different frontmatter keys, even a different file extension, with zero per-harness edits:

One authored source → three native harness files, reproduced from the current fixture. Expected paths and native content were checked, not inferred from exit 0. apm v0.31.0
Harness Deployed path Frontmatter Transform vs. your source
copilot .github/instructions/meridian-checkout.instructions.md description + applyTo unchanged — native .instructions.md
claude .claude/rules/meridian-checkout.md paths only applyTo → paths; description dropped; .md
cursor .cursor/rules/meridian-checkout.mdc description + globs applyTo → globs; description kept; .mdc

The transforms are exactly Chapter 3's “compiled is not byte-identical” rule, now proven on Meridian's own content. Read the two projected files side by side and the point is undeniable — same rule, each in its harness's native shape:

.claude/rules/meridian-checkout.md — applyTo became paths, and description was dropped, as in the original 0.23.1 projection. apm v0.31.0
---
paths:
  - "**/*.{ts,tsx,md}"
---

Use the Meridian `Money` value object for currency math. Treat checkout retries as
idempotent operations keyed by `paymentAttemptId`. Never suggest storing card data in
application logs.
.cursor/rules/meridian-checkout.mdc — applyTo became globs, description was kept, and the extension is .mdc. The current serializer quotes the description. apm v0.31.0
---
description: "Meridian checkout service engineering rules"
globs: "**/*.{ts,tsx,md}"
---

Use the Meridian `Money` value object for currency math. Treat checkout retries as
idempotent operations keyed by `paymentAttemptId`. Never suggest storing card data in
application logs.

Two side effects are worth naming so they are not mistaken for drift. apm install auto-appended apm_modules/ to .gitignore (it is generated, never committed), and it wrote apm.lock.yaml. The current local-only fixture's actual lock has dependencies: [], three deployments ownership records, and the local deployed-file lists and hashes. Fresh 0.31.0 locks omit generated_at; compare the recorded state, not a timestamp (current lockfile contract). The full treatment is Chapter 6. What matters here is what you authored: one manifest, one instruction, and a reviewable file the whole team now shares.

Recap & next

Recap

  • A manifest turns “what this project needs” into reviewable, version-controlled state. In APM that file is apm.yml — the Portability surface, and the input that seeds reproducibility, governance, and provenance in later chapters.
  • You author one input; APM generates the outputs. The manifest is declared intent (what); the lockfile is resolved state (exactly which, Chapter 6).
  • Plain empty-directory apm init writes one file with sensible defaults: identity (name/version/description/author), commented targets, two empty dependencies buckets, includes: auto, and scripts: {}. Only name and version are required to parse.
  • includes: auto consents to the selected local source layout. An explicit list does not filter install discovery, but is exhaustive for plugin packing. You edit source; APM owns its materialized copies, not every file in a harness directory.
  • The scaffold is not the whole schema: license is valid optional metadata and devDependencies keeps author tooling out of the published dependency closure. A hybrid layout does not activate the reserved type: hybrid field.
  • Pin targets for a durable destination choice. Use ref on Git objects, id/version for registry packages, and path for local references. MCP accepts registry strings as well as server objects. Review the declaration, rather than relying on directory names or the last invocation's flags.
  • Meridian committed its first real manifest (v0.1.0): identity, three targets, and one local instruction that apm install deployed to Copilot, Claude Code, and Cursor — one source, three native files, zero per-harness edits.

Next

Meridian's manifest declares its own context beautifully — but it depends on no one else yet. Chapter 5 — Install & Restore fills the dependencies.apm bucket with a real, pinned git source, runs apm install to consume an external package for the first time, and turns the empty scripts map into runnable commands — the everyday install and restore loop.