Chapter 3

Primitives & Harnesses

Know the vocabulary of what APM manages and how those primitives relate to agent harnesses.

Objective

After this chapter you can name the seven primitive types APM manages — instruction, prompt, skill, agent, plugin, MCP server, and hook — tell the three that look alike apart by who triggers them, explain that APM is built on the open AGENTS.md, SKILL.md, and MCP standards rather than replacing them, and describe how supported context is projected or registered for selected harnesses. Naming the primitives is what makes a context declaration portable and a security decision governable in the chapters ahead.

Version scope: the inspected CLI for this update is apm v0.31.0. Material labeled v0.23.1 is a retained historical snapshot, not evidence of a fresh v0.31.0 execution.

Concept/Theory

You can't package what you can't name

In Chapter 1 the team's agent context was an undifferentiated pile — “an AI setup,” “some rules,” “that prompt.” You can't declare a pile in a manifest, pin it in a lockfile, or gate it in a policy. Before any of that, each item needs a type. That is the whole job of this chapter: hand you the nouns. Chapter 2 showed that names and namespaces are what tame ambiguity in package managers; here we give agent context its names.

APM's word for a typed unit is a primitive — “the atomic unit APM ships.” The supported kinds are instructions, skills, prompts, agents, hooks, commands, plugins, and MCP servers (glossary) — though “command” is a prompt as some harnesses project it rather than a separate thing to author, and the experimental canvas type is out of scope here, which leaves seven authorable types. A primitive is not the same as a package: a package is the unit of distribution and can bundle many primitives. An apm.yml is one supported package signal, not a prerequisite for every consumable skill or plugin. Nor is every repository file a primitive: declarations, supported layouts, and classification determine what can deploy (v0.31.0 package types). The declaration-first boundary below makes that distinction concrete.

The primitive vocabulary

APM manages seven primitive types you'll meet daily. The point of the vocabulary is that they are not interchangeable: each has a different trigger, cost, and blast radius. Read the middle column carefully — who (or what) invokes it is the axis that keeps the types straight.

The seven primitive types APM manages, by trigger. Definitions condensed from APM's “what APM manages” table and primitive catalogue (What is APM?, primitives and targets).
Primitive What it is Who invokes it Problem it solves
Instruction Repository-scoped guardrails and coding standards, scoped by file glob Nobody — ambient, read on every turn (always-on) Persistent rules you never have to re-state
Prompt A parameterized, runnable workflow — “a callable program for an LLM” You — explicitly, by name (you run it) A repeatable multi-step task or checklist on demand
Skill Model-invocable know-how — a SKILL.md folder plus bundled resources The model — when a task matches its description (it reaches for it) Specialized capability loaded only when relevant
Agent A persona with its own scope, tools, and system prompt The user or orchestrator selects it as the acting persona A bounded specialist instead of one generalist
Plugin A packaging format bundling related primitives Installed once; projected or registered whole according to format Shipping a set of primitives together
MCP server An external tool or data connection over Model Context Protocol The agent at runtime, when it calls that tool Governed access to outside tools and data
Hook A target-specific callback at a harness event, such as session start or a tool call The harness runtime, automatically at that event Deterministic automation around tool calls

Two more pairs are worth keeping straight. An agent is who is acting — a persona with its own tools and boundaries; a skill is how to do a task — a procedure the acting agent can pull in, and one agent can use many skills. And a plugin is packaging, not another trigger. Legacy Claude-compatible plugins project into individual primitives; recognized Agent Plugins 1.0 follow a whole-unit registration path instead.

Built on standards, not instead of them

APM does not invent its file formats. It is “built on standards: AGENTS.md · Agent Skills / SKILL.md · MCP” (microsoft/apm README). Each standard is an independent, open format APM adopts for one slice of the vocabulary:

  • AGENTS.md — “a simple, open format for guiding coding agents… a dedicated, predictable place to provide context and instructions” that works across many agents (agents.md). APM's instructions compile into AGENTS.md-style files.
  • Agent Skills / SKILL.md — “a lightweight, open format for extending AI agent capabilities… a skill is a folder containing a SKILL.md file” loaded by progressive disclosure (agentskills.io). APM's skill primitive is this format.
  • MCP (Model Context Protocol) — “an open-source standard for connecting AI applications to external systems” (modelcontextprotocol.io). APM declares MCP servers against this protocol.

Standing on shared standards is what makes APM a manager rather than another silo. The standards define the file and protocol formats; APM adds the dependency layer on top — resolution, pinning, a lockfile, policy, and multi-harness compilation. So APM-produced context is readable by tools that never installed APM, and APM can consume primitives authored by people who never used it. Concretely, apm compile emits distributed AGENTS.md context files from your instructions — the open standard is the output, not a proprietary blob (What is APM?).

In APM

One source, many harnesses

A harness is the agent runtime that consumes primitives — Meridian uses GitHub Copilot, Claude Code, and Cursor — and “each harness has its own primitive directory layout and file format” (glossary). A primitive's intent can be harness-independent, but its support and delivery are target-specific. APM sits between the two. Authoring under .apm/ is a useful convention, not the only supported source layout. APM “does not invent a runtime format. It writes the files each tool already understands and stays out of the way at agent runtime” (What is APM?).

Declarations before directories

A directory is a discovery convention; a declaration states what the author intends to ship. For the APM-versus-plugin choice, an apm.yml is eligible when the root also has .apm/ or the manifest declares APM or MCP dependencies. That eligible manifest takes precedence over a co-located plugin marker. A metadata-only manifest with neither does not. Root SKILL.md bundles and nested skill collections are separate supported signals, so this is not an “any apm.yml wins” rule (package layouts and precedence).

A legacy plugin's explicit component declaration replaces discovery for that component. In the controlled example, "skills": ["./skills/kept", "./extra/custom"] deployed only those two skills, not the extra skills/unlisted directory. "skills": [] means no skills; omitting the key permits conventional discovery. A missing declared skill failed installation rather than silently widening the selection. Declarations are exhaustive, not hints. The worked layout check shows the other side of this rule: a supported skill need not be moved into .apm/.

Because each harness has its own format, APM either writes a primitive natively (the harness reads it directly) or compiles it (APM transforms it into a shape the harness understands). The following historical snapshot makes that visible: the public microsoft/apm-sample-package supplied four primitive types, installed at v0.23.1 once with --target copilot and once with --target claude, with no source edits. It is not a new v0.31.0 full-package run.

One source primitive set → two harnesses, two on-disk layouts — and even two different names. Historical install of microsoft/apm-sample-package, 2026-07-02; public network access used. apm v0.23.1
Source primitive (.apm/…) Copilot (.github/…) — native Claude Code (.claude/…) — compiled
instruction instructions/*.instructions.md .github/instructions/*.instructions.md .claude/rules/*.md — renamed to a rule
prompt prompts/*.prompt.md .github/prompts/*.prompt.md .claude/commands/*.md — a /command (mode key dropped)
agent agents/*.agent.md .github/agents/*.agent.md .claude/agents/*.md
skill skills/<name>/SKILL.md .agents/skills/<name>/SKILL.md .claude/skills/<name>/SKILL.md

Read across any row and portability stops being a slogan. The same source instruction becomes a Copilot .github/instructions/ file but is renamed to a Claude rule; the same prompt is a native Copilot prompt but a compiled Claude /command. (Skills for the Copilot family land in the cross-client .agents/skills/ home rather than a Copilot-only directory, which is why that row's left cell reads .agents/, not .github/.) The intent is shared; the native representation differs. Materializing these files does not establish identical agent behavior.

Two plugin delivery contracts

Legacy Claude-compatible plugins are decomposed into target-specific primitives. A root plugin.json declaring the Agent Plugins 1.0 schema selects a different contract: for Copilot, the recognized plugin stays whole under apm_modules/. The local example registered checkout-plugin@apm through APM's generated marketplace and .github/copilot/settings.local.json; it did not create a loose .agents/skills/retry/SKILL.md duplicate. Non-Copilot targets do not receive an automatic flattened fallback (native registration contract).

Loading that registration requires stable Copilot CLI 1.0.81 or newer; APM need not locate or execute Copilot to register it. Registration success is not runtime success. There is also a known v0.31.0 local-path limitation: the tested native plugin installed, but apm audit --ci failed during replay with Native Agent Plugin canonical IR is missing. That is not a network skip or an audit-clean example, nor evidence that every Git-sourced plugin fails.

Targets are the declaration; the harness is the runtime

You don't name a harness directly in the manifest — you list targets. A target is “the declaration… the manifest or CLI selector for which harnesses to compile for,” while “the harness is the runtime that consumes the compiled output” (glossary). In the exact v0.31.0 catalog, all expands to nine default stable targets: copilot, claude, cursor, codex, gemini, opencode, windsurf, kiro, and grok-build. This describes catalog expansion, not a promise that bare install selects all nine. Pin your team's actual targets; package and per-dependency restrictions narrow the authorized set, not expand it.

The versioned matrix also documents stable, explicit-only hermes, antigravity, and the agent-skills meta-target; experimental targets such as grok-cloud stay outside all. IntelliJ-specific integration is MCP-only; its file primitives use Copilot's profile. Kiro now supports agents. For Codex, Gemini, OpenCode, and Hermes, dependency instructions need a separate apm compile for root context (exact v0.31.0 matrix). These are support boundaries, not claims of live runtime tests. Chapter 5 covers target selection in the consumer loop.

Two of the book's four properties are already visible in this layer. Declaring context as typed primitives is the basis of Portability — one declared source set, delivered where its primitive and format are supported. And because each primitive is a named, typed unit, an organization can allow or deny it by category, which is what makes Governance discussable at all. The remaining two arrive downstream: the lockfile records source identity and deployment ownership, with hashes where applicable — the basis of Reproducibility (Chapter 6) and of provenance (Chapter 8).

When to use / pitfalls

Which primitive for the job?

Distinct names prevent category errors. “Add a security review” is ambiguous until you say who triggers it: a persistent instruction that always warns about logging card data, a prompt the reviewer runs on a PR, a skill the model pulls in when it recognises a payment change, or an agent that owns review end to end. Each has a different cost and blast radius, so pick by trigger:

Choosing a primitive by who — or what — triggers it.
If you want… Reach for a… Because
persistent guidance that no one has to invoke manually instruction it's ambient and always on
a named checklist a human kicks off on a PR prompt you run it, explicitly, by name
know-how the model should pull in only when a task matches skill the model reaches for it, keeping context lean
a bounded specialist that owns a task end to end agent it's a persona with its own tools and scope
deterministic automation at a supported agent-runtime event hook the harness fires it at that event, not at package installation
a governed connection to an external tool or dataset MCP server connection configuration is separate from server execution
several of the above shipped as one installable unit plugin its format determines projection versus whole-unit registration

Harness hooks are not APM lifecycle scripts

A hook is a callback for the agent runtime. In the current hook fixture, .apm/hooks/notice.json's SessionStart became Copilot sessionStart but remained Claude SessionStart; APM deployed the callback without running a harness. The event names and native settings are target-specific (hook projections). By contrast, a manifest's lifecycle section can run commands such as the fixture's python record.py on post-install. That is package-manager automation: automatic project execution requires separate lifecycle trust, and changing the lifecycle subtree revokes that trust (lifecycle scripts). APM is not an agent runtime, but “APM never executes code” would be wrong.

The same declaration boundary limits the install-time content scan: APM scans the authorized deployable source set before agent-readable deployment, not every file in the downloaded cache. A source-only README and an unselected skill were outside that set in the controlled probes. Neither a clean Unicode scan nor a content hash proves benign intent or runtime safety (security scope).

Worked example

Meridian's context, finally typed

Here is Meridian's pile from Chapter 1, sorted into its primitive map. This map is not a manifest — it is the same inventory, now with a type and a trigger on every row. Read down the trigger column and the instruction/prompt/skill split snaps into focus: three of these are markdown, but one is always on, one you run, and one the model reaches for.

Meridian's primitive map — every scattered file from Chapter 1, now typed. A map, not yet a manifest or CLI output. Retained conceptual snapshot. apm v0.23.1
# meridian-checkout, now TYPED:  source item  ->  primitive   (who triggers it)
#
checkout backend guardrails (use Money) -> instruction  nobody: read every turn (always-on)
fraud-review PR checklist               -> prompt       you: invoked by name, on demand
secure-review know-how                  -> skill        the model: reached for on payment diffs
API-architect persona                   -> agent        selected as the acting specialist
payment-sandbox tool connection         -> mcp server   the agent at runtime (declared + gated)
#
# not yet needed: plugin (no bundle to ship) . hook (no lifecycle automation)

That map is heading toward a manifest, but guard the distinction: it is not yet a manifest. The shape it will take groups the five rows into two dependency buckets — apm for primitives and mcp for MCP servers — plus the harness targets to build for. The next sketch is illustrative; Chapter 4 teaches the real syntax and pins each source, and Chapter 5 installs it.

Conceptual only — the same map as a manifest shape, showing the two dependency buckets and harness targets. Historical sketch, not a validated project; the eight-target comment describes that snapshot, not the v0.31.0 catalog. apm v0.23.1
name: meridian-checkout-agent-context
targets: [copilot, claude, cursor]   # 3 of APM's 8 default harness targets
dependencies:
  apm: []              # primitives -- declared for real in Chapter 4:
    #  - <checkout-domain instruction>
    #  - <fraud-review prompt>
    #  - <secure-review skill>
    #  - <api-architect agent>
  mcp: []              # MCP servers -- declared + gated here, the server runs elsewhere:
    #  - <payment-sandbox>

One skill, a different source layout

Meridian's result here is still a typed inventory. A separate, current fixture makes the package/layout distinction tangible: its existing skill lives at .claude/skills/checkout-review/SKILL.md, without a package manifest beside it. The consumer declares that directory as a local dependency and selects Copilot as its destination.

Complete apm.yml from core/onboard-skill — a local skill reference, not a source-layout conversion. Existing execution evidence; no network dependency. apm v0.31.0
# Reference an existing skill without moving or translating its source.
name: core-onboard-skill
version: 1.0.0
targets: [copilot]
dependencies:
  apm:
    - path: ./.claude/skills/checkout-review
Run in a scratch copy of backend/examples/updates/1.2/core/onboard-skill, including its hidden .claude directory. Observed exit 0; no agent or server execution. apm v0.31.0
apm install

APM materialized .agents/skills/checkout-review/SKILL.md, leaving the authored skill in its existing layout. The source happened to be Claude-shaped; the declared destination was Copilot. You did not need a second hand-maintained copy or an .apm/ directory, and installing the skill did not invoke it.

Recap & next

Recap

  • A primitive is APM's atomic, typed unit of agent context. Naming the primitives is the prerequisite for everything else — you can't package, pin, or govern what you can't name.
  • There are seven types. The three that look alike differ by trigger: instruction = always on; prompt = you run it; skill = the model reaches for it. An agent is who acts; a plugin is packaging; an MCP server is an external tool connection; a hook responds to harness events, not APM package lifecycle events.
  • APM is built on open standards — AGENTS.md, SKILL.md, and MCP — not a replacement for them; apm compile emits distributed AGENTS.md files.
  • Supported source primitives project into native files: an instruction becomes a Copilot instruction or a Claude rule; a prompt becomes a Copilot prompt or a Claude /command. Compiled is not byte-identical or universally supported. Recognized Agent Plugins 1.0 instead stay whole for native Copilot registration.
  • Targets are the declaration you list; the harness is the runtime that consumes the output. The v0.31.0 catalog is not a promise that every target supports every primitive.
  • Declarations govern discovery: .apm/ is one supported source layout, and an explicit plugin skill list is exhaustive — an empty list means none. Registration, content checks, and connection configuration are not proof of successful or safe runtime behavior.
  • Typed primitives underpin Portability and make Governance a concrete checklist. Meridian typed its pile into a primitive map — the seed of the next chapter's manifest, but not yet a manifest.

Next

You now have the nouns and a typed map. Chapter 4 — The Manifest: apm.yml turns Meridian's primitive map into a valid apm.yml: real dependency syntax, pinned git sources, and the two buckets (apm and mcp) filled in for the first time.