APM-Packages/corentic-package-manager
Tobias J. Endres 9a3b8d62f3 feat(cpm): agent_ref - compose APM-deployed agents into teams
Empirically verified: oh-my-opencode-slim preset entries keyed to an
APM-deployed native OpenCode agent (.opencode/agents/*.md) reconfigure
it in place - model override works, no duplicate agents are created.

- new optional role field 'agent_ref: <name>': persona (prompt,
  identity) comes from the APM primitive; cpm only resolves model_class
  locally and emits a preset entry under the deployed agent's name
- validation: warns when agent_ref target is missing from
  .opencode/agents/ (apm install not run yet) and when 'purpose' is
  redundantly set on agent_ref roles
- collision warning: inline roles shadowing deployed native agents
  suggest using agent_ref instead
- microsoft-design-review example: reviewer role now uses
  agent_ref: design-reviewer, showcasing both role forms
- README: new section on the relationship to APM agent primitives
- blog-review.md: verified findings as material for a blog addendum
2026-08-23 19:37:21 +02:00
..
examples feat(cpm): agent_ref - compose APM-deployed agents into teams 2026-08-23 19:37:21 +02:00
skills/cpm-setup feat(cpm): integrate 'apm install' into the guided flow 2026-08-23 18:24:25 +02:00
.gitignore chore(cpm): ignore __pycache__ 2026-08-23 17:27:06 +02:00
cpm.py feat(cpm): agent_ref - compose APM-deployed agents into teams 2026-08-23 19:37:21 +02:00
README.md feat(cpm): agent_ref - compose APM-deployed agents into teams 2026-08-23 19:37:21 +02:00

Corentic Package Manager (CPM)

A CPM package is an agent package consisting of an APM manifest plus a team configuration (team-profile.yaml). The Corentic Package Manager translates this package into the native configuration of a target harness.

Translation targets:

Target harness Status
oh-my-opencode-slim ✅ implemented
oh-my-pi planned
Codex / Claude / Copilot vision

Deterministic – the CPM does not guess. It assumes no availability and never picks models automatically.

CPM package                 User                        Runtime
apm.yml +            +   model-mapping.yaml   →   cpm render  →  .opencode/
team-profile.yaml        (model class →               oh-my-opencode-slim.json
(roles, model             concrete model ID)          (or later: native config
 classes, MCPs/skills)                                of another harness)

Prerequisites

  • Python 3.10+ with PyYAML (pip install pyyaml)
  • OpenCode with the oh-my-opencode-slim plugin installed
  • A CPM package with a team-profile.yaml

Quickstart

# 1. Create the model mapping – interactively, choosing from your opencode models
python3 cpm.py init --fill

# 2. Guided: read team profile, check/fill mapping, render, install primitives
cd <project>
python3 /path/to/cpm.py setup --package /path/to/package

# 3. Activate
cd /path/to/package     # the config lives in <package>/.opencode/
opencode
/preset acme-job-applications
# Reload OpenCode -> team is active (by design: no hot-swap)

Alternatively manually:

# 1. Create the model mapping once
mkdir -p ~/.config/cpm
cp examples/model-mapping.yaml ~/.config/cpm/model-mapping.yaml
$EDITOR ~/.config/cpm/model-mapping.yaml   # adapt to your own models

# 2. Validate (dry run)
python3 cpm.py check --package examples/package

# 3. Render + install package primitives
cd /path/to/package
python3 /path/to/cpm.py render --package .
apm install        # deploys skills/instructions from APM dependencies

The three files

File Owned by Purpose Edit manually?
team-profile.yaml Package author Roles, model classes, MCP/skill allowlists per role Yes (author)
~/.config/cpm/model-mapping.yaml User Model class → concrete model ID (+ optional variant/temperature) Yes (user)
.opencode/oh-my-opencode-slim.json generated Preset with concrete model IDs No – always re-render

Plus a provenance file .opencode/oh-my-opencode-slim.cpm-provenance.json documenting which role received which model.

Command reference

python3 cpm.py init   [--mapping FILE] [--fill]          # create/fill mapping
python3 cpm.py setup  --package DIR [--mapping F] ...    # guided flow
python3 cpm.py render --package DIR [--mapping F] [--output F] [--dry-run]
python3 cpm.py check  --package DIR [--mapping F]        # dry-run validation

init – create the mapping

Creates ~/.config/cpm/model-mapping.yaml (if not present). With --fill, all PLACEHOLDER classes are asked interactively: the CPM lists every model from your opencode.json(c), you pick by number or enter a model ID manually.

setup – guided flow

The one-command path: shows the team profile in plain language, creates a missing mapping, interactively asks for missing model classes (same selection as init --fill), renders and then handles package primitives: if the package declares APM dependencies, setup offers to run apm install for you (or prints the manual command if the apm CLI is not installed).

render / check

Option Default Meaning
--package . CPM package directory containing team-profile.yaml
--mapping ~/.config/cpm/model-mapping.yaml Local mapping table
--output <package>/.opencode/oh-my-opencode-slim.json Target file
--team-file team-profile.yaml (alternatively team.yaml) Alternative profile filename

Merge behavior: Existing presets in the target file remain untouched; only the preset for this team ID is replaced. Multiple packages coexist in the same project. Activation is explicit via /preset <name>.

Team profile schema (corentic.team-profile/v1)

schema: corentic.team-profile/v1
id: acme.job-applications          # becomes the preset name (namespaced)
description: ...

roles:
  - id: researcher                 # domain role
    purpose: ...                   # becomes prompt/description for custom agents
    runtime_agent: librarian       # optional: harness builtin or 'custom'
                                   # (default: role id as custom agent)
    model_class: fast-research     # required, must exist in the mapping
    capabilities:
      mcps: [websearch, openviking]  # MCP allowlist ([] = none)
      skills: [job-application]      # skill allowlist ([] = none)

  - id: reviewer                   # Form B: bind an APM-deployed agent
    agent_ref: design-reviewer     # persona comes from .opencode/agents/
    model_class: high-reasoning    # resolved locally, injected via preset

The runtime_agent field is deliberately harness-neutral: the adapter decides how the role is represented in the target harness. Builtins recognized by the oh-my-opencode-slim adapter: orchestrator, oracle, librarian, explorer, fixer, designer, council, observer. Everything else (or runtime_agent: custom) creates a custom agent including prompt and orchestratorPrompt derived from the purpose field.

Relationship to APM agent primitives

APM packages can carry their own agent definitions (.apm/agents/*.agent.md, deployed to .opencode/agents/). A CPM team profile composes these deployed agents instead of duplicating them:

roles:
  - id: reviewer
    agent_ref: design-reviewer   # persona comes from the APM-deployed primitive
    model_class: high-reasoning  # model class resolved locally, injected via preset

With agent_ref:

  • Persona (prompt, identity) comes from the APM primitive – purpose is ignored. One source of truth.
  • Model is resolved by the CPM from your local mapping and injected via the preset entry keyed to the agent's name. Verified empirically: oh-my-opencode-slim presets reconfigure APM-deployed native agents (model override works, no duplicate agents are created).
  • The team may only restrict what the primitive declares, never redefine it – same tighten-only philosophy as APM policies.

Roles without agent_ref use the inline form (purpose + capabilities) for packages that ship no agent primitives. cpm check warns when an inline role would shadow a deployed native agent – consider agent_ref instead.

Note: if an APM agent primitive freezes a concrete model you do not have, prefer upstream primitives that omit model: (like microsoft/apm-sample-package does) and let the CPM resolve it locally via model_class.

Mapping formats

model_classes:
  fast-research: ollama/qwen3.5:9b              # simple
  high-reasoning:                               # extended
    model: ollama/qwen3.6:35b-a3b-q4_K_M
    variant: thinking
    temperature: 0.3

All extra fields are passed through 1:1 into the agent entry of the preset.

What the CPM checks

  1. Team profile present, schema known, id and roles present
  2. Every model_class has a mapping entry (otherwise error with fix hint)
  3. MCPs matched against ~/.config/opencode/opencode.json(c) (warning, not fatal)
  4. Roles without MCPs/skills -> note

Examples

Example Based on Team
examples/package/ Own job-application package (OpenViking + ShareLaTeX) acme.job-applications – Researcher, Writer, Checker, Notifier
examples/microsoft-design-review/ Official microsoft/apm-sample-package acme.design-review – Reviewer (agent_ref → deployed design-reviewer), Style-Checker (Fixer), Accessibility-Auditor
examples/microsoft-issue-autopilot/ Official apm-issue-autopilot from the microsoft/apm repo acme.issue-autopilot – Triager, Shepherd, PR-Writer, Reviewer (+ GitHub MCP)

The two Microsoft examples are thin CPM wrappers: their own apm.yml pulls the official package as a versioned APM dependency (microsoft/apm-sample-package#v1.0.0 or monorepo subpath microsoft/apm/packages/apm-issue-autopilot) and only adds the team recommendation.

cd corentic-package-manager/examples

# Dry run: what would be generated?
python3 ../cpm.py check --package package --mapping model-mapping.yaml
python3 ../cpm.py check --package microsoft-design-review --mapping model-mapping.yaml
python3 ../cpm.py check --package microsoft-issue-autopilot --mapping model-mapping.yaml

# Render into the example project
python3 ../cpm.py render --package package \
  --mapping model-mapping.yaml \
  --output package/.opencode/oh-my-opencode-slim.json

Expected result: preset acme-job-applications with five agents – orchestrator, librarian (alias researcher), custom agent writer, oracle (alias checker), custom agent notifier. The researcher gets no LaTeX access, the writer sees no job-search tools, the notifier gets no external access at all.

Then:

cd package && opencode
/preset acme-job-applications

Using the team

Rendering + activation only configures the agents. To actually work with the team:

  1. Install primitives. If the package declares APM dependencies, run apm install in the package directory (cpm setup offers this automatically). This deploys the skills/instructions that role allowlists reference into .opencode/skills/ etc.
  2. Reload OpenCode. Agents (preset) and skills are loaded at startup.
  3. Sanity check. In OpenCode: /agents lists the team members; "ping all agents" verifies they respond.
  4. Work via the orchestrator. Just state your task in normal language – e.g. "Review the components in src/ against our design standards". The orchestrator decomposes it and delegates according to the generated routing prompts: style violations → @style-checker, design judgment → @reviewer, audits → the auditor agent. It then consolidates the findings.

Two things to know:

  • The preset switch (/preset) and skill loading both require an OpenCode restart/reload – by design.
  • Activating a preset writes its name to your global user config (~/.config/opencode/oh-my-opencode-slim.json), so it persists across sessions and projects until you switch again.

Troubleshooting

Problem Cause/Fix
Model class X is not mapped Add to the mapping, re-render
Warning: MCP ... not configured Set up the MCP in opencode.jsonc or remove it from the profile
/preset does not show anything new Start opencode in the directory containing .opencode/ and reload; presets do not apply mid-session
Agent references a skill that does not exist Run apm install in the package directory – the preset only configures agents, primitives come from APM
Preset lost after manual editing The file is generated – changes belong in the mapping or team profile
YAML errors Follow the line number in the error message

Deliberate limits

  • The CPM does not start agents and orchestrates nothing at runtime – that is the job of the target harness.
  • MCP/skill allowlists are capability scoping, not a sandbox. Irreversible actions need server-side authorization + human approval.
  • Model availability is checked against opencode.json(c) names, not via API health checks.