Chapter 5

Install & Restore

Install and restore a project's agent context with the daily APM consumer loop.

Objective

After this chapter you can run APM's four-command consumer loop — apm init, apm install <pkg>, bare apm install, and apm run <script> — and tell its two install jobs apart: add (grow the manifest and re-lock) versus restore (replay unchanged intent from its lockfile). You will read the lockfile entry an install writes, know why a no-target install stops with exit 2 instead of guessing a harness, and know why a script that names a harness needs that runtime CLI on your PATH. This is the operation that turns Chapter 4's declaration into the same supported context in each tool's native format — the moment Portability stops being a promise and becomes a fact.

Edition 1.2 — current behavior: APM 0.31.0. Current examples below were independently checked on 2026-09-16. The original full-package Meridian loop and live Copilot recording remain explicitly historical v0.23.1 examples; their outputs, hashes, and timings have not been relabeled. Use the small current fixtures to follow along with this edition.

Concept/Theory

A manifest is a recipe, not a meal

Chapter 4 gave Meridian an apm.yml. On its own, that file changes nothing about what an agent sees: no instruction is in force, no prompt is available, no MCP server is wired up. A manifest is a declaration of what should exist; something still has to make it exist. Install is that something. It resolves declared dependencies, materializes their content, and integrates eligible primitives and local sources for the selected, supported targets. The resulting lockfile records installed state for the next contributor (APM 0.31.0 install reference).

We call this move materialization: turning the declared state (apm.yml plus its lockfile) into real files each tool reads natively. It is where the book's first property becomes observable. Chapters 1–4 argued that scattered, hand-copied context drifts; a manifest only ends that drift if there is a single, mechanical way to realize its intent. Portability is not cross-harness byte equality: Copilot instructions, Claude rules, and Cursor rules can have different frontmatter while expressing the same source instruction. Reproducibility compares each target's output with the corresponding output from the same sources, lockfile, targets, and CLI version. Editing the manifest without installing leaves the declared and materialized states out of sync — the very gap Chapter 6 formalizes.

Install is not “just downloading packages,” either. Think resolve, policy gate, scan, integrate, lockfile: applicable policy and security checks gate agent-readable deployment. Acquisition, cache, and staging writes can happen earlier; the scan covers the authorized deployable content, not every file in a source repository. That boundary is Chapter 8's subject; here it is enough to see install as gated deployment, not a copy (install behavior and safety). The package and native-rule installs in this chapter record apm.lock.yaml, seeding Reproducibility. An empty or compile-only operation need not produce a lockfile; successful exit alone does not prove materialization.

The daily loop: four commands, one habit

APM's everyday surface is deliberately tiny. The consumer ramp names “the four commands you'll use almost every day,” then draws the line: “That's the loop. Everything else is either lifecycle automation (update, outdated, audit) or a workflow extension” (Use APM packages). The point is a habit, not a flag reference — four verbs that map onto four real moments in a team's week:

The four-command consumer loop, each verb mapped to the moment it belongs to. Definitions from the consumer ramp (Use APM packages).
Command Job The everyday moment (Meridian)
apm init scaffold the manifest — one-time per project when the manifest was born in Chapter 4
apm install <pkg> add a dependency and re-lock Lena needs a new shared skill
apm install restore unchanged intent from the lockfile Anika clones a fresh repo
apm run <script> run a declared script (experimental) anyone runs the checkout-review prompt

Scaffolding with apm init is one-time; the other three verbs recur. Heavier maintenance — update, outdated, audit — is deliberate work saved for Chapter 7, not part of the daily path. And the loop does not grow when Governance arrives: “Consumer commands are unchanged when policy is enforced — you'll just see more [x] blocks at install time when something is denied” (Use APM packages). The same four verbs survive Chapters 8–9 intact.

Restore vs. add: one command, two jobs

The single command apm install does two conceptually different things depending on whether you hand it an argument. “With no arguments it installs everything from apm.yml. With one or more PACKAGE_REF arguments it adds those packages to apm.yml … and installs only what was added” (apm install). With an unchanged manifest and matching lockfile, bare install is a restore: it reuses the locked dependency graph. apm install <pkg> is an add — it declares a dependency in dependencies.apm, installs it, and re-locks.

These are the two halves of team dependency work. Add is how the manifest grows — a reviewable change to project state, exactly like npm install <pkg> editing package.json. Restore is how everyone else catches up without thinking, and it is where the whole book's onboarding promise lands: git clone + apm install replaces the wiki page, the Slack thread, and the “copy my prompt files” dance from Chapter 1. Crucially, unchanged intent replays; changed intent is reconciled. Editing a dependency's ref, changing local sources, or installing without a lockfile can cause ordinary install to resolve or materialize something new. For a deliberate refresh of an unchanged declaration, use the consent-gated apm update workflow in Chapter 7. Do not confuse replay with refresh — but do not expect an old lock to overrule a ref you explicitly changed, either (locked replay and fresh resolution).

In APM

One install command, two jobs

You already met apm init in Chapter 4: it “creates a minimal apm.yml … so you can start running apm install immediately” (apm init). Plain apm init --yes in an empty directory still creates only apm.yml — no source tree, deployment, or lockfile. Existing context has a separate onboarding path below. Then the other three verbs do their usual jobs: add and restore materialize the manifest, targeting decides where files land, and apm run executes a declared script.

Add: apm install <pkg> grows the manifest and re-locks

Meridian's team wants a public package's primitives. Its original recorded add pinned the direct dependency, resolved its transitive graph, deployed supported primitives into all three harnesses, and updated both apm.yml and the lockfile. This is materialization in action. The following full-package recording remains historical; the current exercise deliberately selects just one immutable public skill rather than claiming to have rerun this larger graph.

Historical Meridian add: the full public sample and its transitive dependency, recorded 2026-07-02. The add changed the manifest and re-locked; the target flag won. needs network apm v0.23.1 — not rerun at 0.31.0
$ apm install microsoft/apm-sample-package#v1.0.0 --target copilot,claude,cursor
  [*] Validating 1 package...
  [+] microsoft/apm-sample-package#v1.0.0
  [*] Updated apm.yml with 1 new package(s)                       # <- add mutates the manifest
  [>] Installing 1 new package...
  [i] Targets: claude, copilot, cursor  (source: --target flag)   # <- flag beat apm.yml targets:
    [+] microsoft/apm-sample-package #v1.0.0 @fb285168
    |-- 2 prompts, 3 agents, 4 commands, 3 rules, 1 skill  (across 3 targets)
    [+] <project root> (local)
    |-- 3 rule(s) adopted -> 3 targets  (files unchanged)         # <- your local instruction preserved
  [!] 1 dependency unpinned: github/awesome-copilot -- add #tag or #sha to prevent drift
  [*] Installed 2 APM dependencies in 56.0s.                      # exit 0

That historical add wrote one pinned line into the manifest — owner/repo#<ref>, the explicit-ref form from Chapter 4:

Historical apm.yml excerpt after the full-package add — the inserted dependency. apm v0.23.1 — not rerun at 0.31.0
dependencies:
  apm:
    - microsoft/apm-sample-package#v1.0.0   # pinned ref -> deterministic restore
  mcp: []

And it wrote the lockfile record — the by-product that seeds Reproducibility. Notice the two fields that make restore exact: resolved_commit (the precise SHA the tag v1.0.0 pointed at) and content_hash (the fingerprint of the materialized package tree). Separate deployed-file hashes describe the native outputs. You do not author this file; APM generates it. Chapter 6 studies it in full — here, just notice it appear:

Historical generated lock excerpt for the full package — exact commit plus package-tree hash, not a complete restore file. apm v0.23.1 — not rerun at 0.31.0
# apm.lock.yaml (generated)
dependencies:
- repo_url: microsoft/apm-sample-package
  resolved_commit: fb2851683be0e0e7711421d518bd8dba23b0b1f6       # the exact SHA it became
  resolved_ref: v1.0.0                                            # the ref you pinned
  content_hash: sha256:744cca54cc8ff7ca90aa1dd621c2f98c6291cd793815afe8518001cc94b8aba9
  package_type: apm_package

One pinned line plus one command produced roughly twenty real files across three tools — no hand-copying, pinned by commit and hash for the next person. The manifest edit was declaration; this command was materialization. (The [!] 1 dependency unpinned warning is about the sample's own transitive dependency — the recorded lock pinned it by resolved_commit. Replaying that unchanged graph differs from resolving it afresh without the lock — a Chapter 6 concern, not an install failure in this recording.)

Restore: bare apm install replays unchanged intent

Re-running apm install with no argument is the onboarding path from the Meridian beat. With unchanged declarations and a matching lockfile, Git dependencies reuse the locked graph, including transitive manifests. Local source files must also remain unchanged to reproduce their outputs. An intact apm_modules/ tree or cache can serve a replay; a fresh clone may need network access to hydrate the same locked source. Cache availability affects acquisition, not which commit the lock selected.

Historical warm restore of the full-package fixture. Its lock bytes were unchanged; the recorded timing and legacy generated_at are not current reproducibility tests. apm v0.23.1 — not rerun at 0.31.0
$ apm install                                              # bare = restore (no package argument)
  [i] Targets: claude, copilot, cursor  (source: apm.yml)  # <- now from the manifest, not a flag
    [+] microsoft/apm-sample-package #v1.0.0 @fb285168 (cached)   # <- served from apm_modules/
    |-- (files unchanged)
    [+] <project root> (local)  |-- 3 rule(s) adopted (files unchanged)
  [*] Installed 1 APM dependency in 10.3s.                 # exit 0 (vs 56.0s cold)
# apm.lock.yaml is byte-identical -- generated_at unchanged: 2026-07-02T00:01:57...+00:00

In 0.31.0, newly generated locks omit generated_at; legacy locks may retain it. Compare complete lock bytes and the expected native outputs, not a clock. The current local and public fixtures produced byte-stable locks on replay. Some restores now print No changes -- install state already up to date; others still print an installed dependency count. Neither wording is a universal completeness check (lockfile and replay contract).

For CI, apm install --frozen requires an existing lock and performs limited structural checks. It can still fetch locked content into a cold cache. In 0.31.0, its direct-dependency presence check skips local paths: a newly declared local dependency can still deploy and rewrite the old lock with exit 0 (frozen consistency check). It is not an on-disk content audit; that separate job belongs to apm audit. We use both in the current exercise, then develop them in Chapter 6 and Chapter 7.

Targeting at install time — no implicit “one true harness”

Install deploys into targets — the target-vs-harness split from Chapter 3, now doing visible work. Which harnesses receive files is resolved by an explicit precedence chain, not a hardcoded favorite: --target > apm.yml targets: > a configured default > auto-detection of harness directories already present (apm install). In the add above, the --target flag won (source: --target flag); on the next bare install, the manifest won (source: apm.yml). With an existing manifest, the flag is a per-invocation override; it does not replace the authored target list. Bootstrap is different: a positional add with no manifest can create apm.yml and persist the supplied targets. Pin and review targets: for a durable choice rather than relying on a flag from someone's earlier command (selection and bootstrap).

Targets authorize destinations; package restrictions can narrow that set, not expand it. When a manifest's target set changes, install reconciles recorded ownership rather than treating whole harness directories as disposable. The tested input was an eligible APM package with a co-located legacy plugin.json marker. In the marker-free control, reducing four manifest targets to Copilot removed stale managed outputs for the other targets while retaining Copilot's outputs and the package source. Source files, surviving shared owners, and unrelated human-authored context are not blanket cleanup targets (deployment ownership).

This control omitted only pkg/plugin.json before first install; install and CI audit exited 0 before and after contraction. With the legacy marker present, cleanup still worked, but a 0.31.0 replay defect made CI audit expect legacy commands, exit 1, and modify apm_modules/ (not authored sources, the lock, or deployed files).

Meridian's three targets consume native per-file rules. Do not generalize that to every profile: some instruction targets, such as Codex, need explicit apm compile for aggregate AGENTS.md context. The local-instruction-only Codex probe exited 0 without a lockfile or AGENTS.md; compilation was a separate step. Default compilation also preserved an existing unmarked, hand-authored root file (target matrix). This is Portability with explicit boundaries, not an implicit “one true harness.”

Run a declared script: apm run (experimental) and apm preview

The fourth verb executes a script from the manifest's scripts: block. It is stamped experimental — “the run command surface is marked experimental … flags and behavior may change before 1.0” (apm run). It is also more than literal shell: for a script like copilot -p .apm/prompts/checkout-review.prompt.md, apm run compiles the .prompt.md (frontmatter stripped) and injects the resulting content into copilot -p <content> — the prompt file is compiled, not passed verbatim. Then it shells out to the named runtime. Critically, APM does not ship the runtimes: “the runtime CLI must be on your PATH” (Run scripts). That requirement follows the script: an ordinary shell script does not require an AI runtime.

apm preview is the compile-only sibling: it writes the script's compiled prompt to .apm/compiled/ without invoking the runtime. For a local prompt this is offline and incurs no inference cost, ideal when you only need to inspect compilation. The current small probe declares review: "copilot -p .apm/prompts/review.prompt.md"; its shorter filename is separate from Meridian's checkout-review.prompt.md.

Current scripts-probe fixture: preview compiles a local prompt without running Copilot. Windows output, display formatting condensed; observed exit 0. Offline. apm v0.31.0 — verified 2026-09-16
$ apm preview review
Original command: copilot -p .apm/prompts/review.prompt.md
Compiled command: copilot
Compiled prompt files: .apm\compiled\review.txt
[*] Preview complete! Use 'apm run review' to execute.

A successful preview proves compilation, not a successful agent session. The historical worked example below records a real Copilot invocation; it was not repeated for this update. Current execution checks used only a harmless shell command and a deliberately missing executable.

When to use / pitfalls

Restore or add? Pick by intent

Both jobs use the same command; the argument distinguishes add from the normal install path. Choose by intent: unchanged manifest and lock mean replay, while edited declarations require reconciliation.

Which form of apm install to reach for, by intent.
You want to… Run… Because
set up a freshly cloned repo apm install (bare) with the committed lock and sources, restore replays the pinned set
add a new dependency to the project apm install <pkg>#<ref> add writes it into apm.yml and re-locks — a reviewable diff
re-materialize after editing apm.yml by hand apm install (bare) it reconciles the changed declaration; the resolved state and lock may change
move to newer versions apm update (Chapter 7) this is the recommended consent-gated refresh workflow, not an accidental side effect of onboarding

Worked example

Meridian, committed as v0.2.0

Two everyday actions carried Meridian from the Chapter 4 manifest (v0.1.0: one local instruction, three targets) to v0.2.0: an add pinned a public dependency, and a scripts: entry declared a review workflow. The authored snapshot below preserves that story milestone. It and the following list/run recordings belong to the original v0.23.1 worked example, not a fresh execution of the full graph on 0.31.0.

Historical Meridian apm.yml at v0.2.0 — one pinned full package plus one review script. public dependency needs network apm v0.23.1 — not rerun at 0.31.0
name: meridian-checkout-agent-context
version: 0.2.0
description: Shared AI-agent context for the Meridian checkout service.
author: Meridian Checkout Team
targets:
  - copilot
  - claude
  - cursor

dependencies:
  apm:
    - microsoft/apm-sample-package#v1.0.0     # added this chapter -- pinned, deterministic
  mcp: []
includes: auto
scripts:
  review: "copilot -p .apm/prompts/checkout-review.prompt.md"   # apm run review -> the checkout-review prompt

In that recording, apm list showed the declared script, and apm run review invoked it with the copilot runtime available on PATH:

Historical listing of the v0.2.0 manifest's script. apm v0.23.1 — not rerun at 0.31.0
$ apm list
              Available Scripts
  Script   Command
  review   copilot -p .apm/prompts/checkout-review.prompt.md
Historical real apm run review execution — experimental. Copilot supplied the runtime and incurred the cost; this timing is not a current benchmark. needs harness CLI and its service access apm v0.23.1 — not rerun at 0.31.0
$ apm run review                                           # experimental; runtime CLI must be on PATH
  [>] Running script: review
  Compiling prompt...          +- .apm/prompts/checkout-review.prompt.md
  Executing copilot runtime... Command: copilot   Prompt content: 253 characters   # <- content injected
  ... copilot runs the checkout review ...
  [+] Copilot execution completed successfully (188.68s)
  [*] Script executed successfully!                        # exit 0 (the runtime did -- and billed -- the work)

That is the loop, end to end: the team added a dependency and a script to reach v0.2.0, and Anika restored the whole thing with git clone + apm install. Same declaration, supported context in Copilot's and Cursor's native forms — Portability, materialized.

Edition 1.2: reproduce a small current setup

Now test the same materialization/replay boundary with small, controlled inputs. Use APM 0.31.0, not a moving latest release; the official installation guide covers the available installation channels. The book repository contains three fixtures under backend/examples/updates/1.2/core/. They isolate behaviors without changing Meridian's v0.2.0 milestone or substituting a new result for the historical full-package graph.

Start with local-instruction for an offline exercise. In a fresh, short-path scratch directory outside the book checkout, copy the selected fixture's apm.yml and the authored sources listed below, including hidden directories. For this first-materialization exercise, start without apm_modules/, generated outputs, or a lockfile; keep the fixture's recorded apm.lock.yaml separately for comparison. Real clone-and-restore onboarding instead brings the committed lock along from the start. Do not run init over either copy.

Exact current fixture inputs and expected native files. All fixtures pin targets; these are file-materialization checks, not live harness sessions. APM 0.31.0; independently checked 2026-09-16.
Fixture Source accompanying apm.yml Expected result
local-instruction — offline .apm/instructions/meridian-checkout.instructions.md, the instruction from Chapter 4 .github/instructions/meridian-checkout.instructions.md, .claude/rules/meridian-checkout.md, and .cursor/rules/meridian-checkout.mdc; the authored source stays in place.
onboard-skill — offline .claude/skills/checkout-review/SKILL.md, with the already-declared, target-pinned manifest .agents/skills/checkout-review/SKILL.md for Copilot; the original Claude-layout skill remains the source, not a newly authorized Claude deployment.
pinned-skill — needs public Git access on a cold cache No local source tree; the full manifest below selects one public skill at an immutable commit .agents/skills/style-checker/SKILL.md shared by Copilot/Cursor, and .claude/skills/style-checker/SKILL.md for Claude.

For the network-backed variant, this is the complete input manifest. Selecting only style-checker avoids the full sample package's floating transitive dependency:

backend/examples/updates/1.2/core/pinned-skill/apm.yml — immutable source identity and three explicit targets. needs network on a cold cache apm v0.31.0 — verified 2026-09-16
# A single public skill at an immutable commit, with no transitive dependencies.
name: pinned-public-skill
version: 1.0.0
targets: [copilot, claude, cursor]
dependencies:
  apm:
    - git: microsoft/apm-sample-package
      ref: fb2851683be0e0e7711421d518bd8dba23b0b1f6
      path: .apm/skills/style-checker
  mcp: []
includes: auto

In each selected scratch directory, materialize once, repeat with unchanged inputs, then check the two different boundaries: frozen install's limited structural checks, with the local-path exception, and CI audit's integrity checks. These last two commands are a verification exercise, not extra everyday verbs to memorize:

Run consecutively inside each chosen current fixture copy. Independent execution confirmed 0 / 0 / 0 / 0 for install, replay, frozen install, and CI audit in all three. Local fixtures are offline; the public skill needs network on a cold cache. apm v0.31.0 — commands verified 2026-09-16
apm install
apm install
apm install --frozen
apm audit --ci

Check the native files in the table, not just the exit code. The repeated install and frozen install left each complete lock byte-identical. A separate cold-cache public-skill test started with only the manifest and lock: frozen install fetched the pinned content, exited 0, and preserved the lock bytes. That is reproducible acquisition, not a promise of offline access.

Selected fields from the genuine pinned-skill/apm.lock.yaml — identity and content, not a timestamp. Use the complete fixture lock for restore, never this excerpt. apm v0.31.0 — verified 2026-09-16
lockfile_version: '1'
apm_version: 0.31.0
dependencies:
- repo_url: microsoft/apm-sample-package
  name: style-checker
  host: github.com
  resolved_commit: fb2851683be0e0e7711421d518bd8dba23b0b1f6
  resolved_ref: fb2851683be0e0e7711421d518bd8dba23b0b1f6
  version: unknown
  virtual_path: .apm/skills/style-checker
  is_virtual: true
  package_type: claude_skill
  content_hash: sha256:867912713bf45048211440b81ea0ced396ad0a0993447dd540fe5c219722d318

None of these three newly generated locks contains generated_at. Their full files also record deployments ownership and per-file hashes. The public skill's version: unknown does not weaken its explicit Git commit, and its package-tree hash is intentionally different from the historical full-package hash above. Top-level fields need not be identical across lock types: the local-instruction-only lock has no apm_version field.

Keep the hash meanings separate: deployed UTF-8 text hashes normalize CRLF line endings, whereas package-tree hashes describe the actual materialized bytes. Neither means that APM rewrites all source files, nor that different harness-native projections must have identical bytes (lockfile hashes and ownership). The payoff is still Anika's simple habit: review intent once, then restore and check the recorded result instead of copying context by hand.

Recap & next

Recap

  • Install materializes the manifest. apm.yml is a recipe; install is the meal — it resolves, gates, scans, and deploys supported content. The package and native-rule examples record apm.lock.yaml; an empty or compile-only operation may not. This is where Portability becomes a fact, and it seeds Reproducibility as a by-product.
  • The daily loop is four commands, one habit: apm init to scaffold once, apm install <pkg> to add, bare apm install to restore, apm run <script> to run. Everything heavier is lifecycle (Chapter 7). Discovery with consented apply is a separate migration aid: it declares existing packages without installing them.
  • One command, two jobs. Bare install replays unchanged intent from the locked graph; apm install <pkg> adds a declaration and re-locks. Ordinary install reconciles edited intent, and cold restores may need network. Compare lock bytes and native outputs — new locks have no generated_at timestamp to compare.
  • Targets resolve by an explicit chain (--target > apm.yml targets: > configured default > auto-detect). The flag overrides an existing manifest for one run; bootstrap can persist it. Work needing a destination but lacking a target exits 2, while mixed successful installs can include skips. Check what arrived.
  • apm run is experimental and executes the command the script names; a harness script needs its CLI on PATH. apm preview compiles local prompts without runtime cost, but proves neither agent execution nor installation completeness.
  • Meridian reached v0.2.0 (one pinned dependency + one script), and Anika onboarded with git clone + apm install — the Chapter 1 drift, solved.

Next

The current fixtures left you with genuine lockfiles and unchanged replay results. Chapter 6 — The Lockfile & Reproducibility opens that file up: what resolved_commit and content_hash guarantee, how apm install --frozen supplies a limited structural CI gate with the local-path exception, distinct from content audit, and why reproducing the recorded state — not preserving a timestamp — supports the supply-chain story.