diff --git a/blog-review.md b/blog-review.md index 5114c61..4092902 100644 --- a/blog-review.md +++ b/blog-review.md @@ -579,3 +579,20 @@ Direkt vor dem ersten team-profile-Codeblock (Abschnitt „Die APM-OMOS-Idee"): Ersatzfassung: > „Spawnfile v0.1 besitzt derzeit keinen gebündelten OpenCode-Adapter. OpenCode wird als explorativer Runtime-Kandidat geführt; ein natives OpenCode-Artefakt wird daher aktuell nicht erzeugt." Zusätzlich (aus unserer Code-Analyse): In v0.1 implementiert kein Adapter `compileTeam()` – Teams degradieren überall zu Kontext-Dokumenten. + +--- + +## Blog-Nachtrag-Material: APM-Agent-Primitives als Persona-Quelle (empirisch verifiziert) + +Aus dem Praxistest mit dem offiziellen microsoft/apm-sample-package (August 2026): + +**Befund:** Ein Preset-Eintrag, der auf den Namen eines APM-deployten nativen OpenCode-Agenten zielt, konfiguriert diesen Agenten – inklusive Modell-Override. Kein Duplikat, die Persona aus der deployten Datei bleibt erhalten. Verifiziert am design-reviewer-Agenten: injiziertes `ollama/qwen3.5:9b` wurde zur Laufzeit gemeldet („Design-Reviewer · Qwen 3.5 9B"). + +**Konsequenz für das CPM-Format:** Die Rollen-Definition hat zwei Formen: + +1. **Inline-Persona** (`purpose` + `capabilities`) für Packages ohne eigene Agent-Primitives +2. **`agent_ref`** für Packages mit `.apm/agents/`: Die Rolle bindet den deployten Agenten ins Team, `model_class` löst das Modell lokal auf + +Damit schrumpft die CPM-Rolle bei Form B auf ihr Minimum: Team-Slot + Modellklasse (+ optionale Einschränkung der Whitelist). Prompt und Tools kommen aus dem Primitive – eine Quelle der Wahrheit, „tighten only". + +**Für den Blogpost:** Der Abschnitt „Was OMOS tatsächlich begrenzen kann" kann um diesen Befund ergänzt werden; außerdem ist der Design-Review-Beispiel-Flow jetzt vollständig end-to-end gelaufen (cpm setup → apm install → /preset → Delegation → Modell-Verifikation). diff --git a/corentic-package-manager/README.md b/corentic-package-manager/README.md index 9dd4729..dd6bced 100644 --- a/corentic-package-manager/README.md +++ b/corentic-package-manager/README.md @@ -114,10 +114,35 @@ roles: 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: + +```yaml +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](https://github.com/microsoft/apm-sample-package) does) and let the CPM resolve it locally via `model_class`. + ### Mapping formats ```yaml @@ -143,7 +168,7 @@ All extra fields are passed through 1:1 into the agent entry of the preset. | 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](https://github.com/microsoft/apm-sample-package) | `acme.design-review` – Reviewer (Oracle), Style-Checker (Fixer), Accessibility-Auditor | +| `examples/microsoft-design-review/` | Official [microsoft/apm-sample-package](https://github.com/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`](https://github.com/microsoft/apm/tree/main/packages/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. diff --git a/corentic-package-manager/cpm.py b/corentic-package-manager/cpm.py index a8e6238..a650c99 100644 --- a/corentic-package-manager/cpm.py +++ b/corentic-package-manager/cpm.py @@ -142,12 +142,23 @@ def sanitize_preset_name(team_id: str) -> str: # Translation -def build_preset(profile: dict, mapping: dict) -> tuple[dict, dict, list[str]]: +def deployed_native_agents(package_dir: Path) -> set[str]: + """Names of APM-deployed native agents (.opencode/agents/*.md).""" + agents_dir = package_dir / ".opencode" / "agents" + if not agents_dir.exists(): + return set() + return {p.stem for p in agents_dir.glob("*.md")} + + +def build_preset(profile: dict, mapping: dict, + package_dir: Path | None = None) -> tuple[dict, dict, list[str]]: """Returns (preset, custom_agents, warnings).""" preset: dict = {} custom_agents: dict = {} warnings: list[str] = [] + native = deployed_native_agents(package_dir) if package_dir else set() + for role in profile["roles"]: role_id = role.get("id") if not role_id: @@ -170,6 +181,30 @@ def build_preset(profile: dict, mapping: dict) -> tuple[dict, dict, list[str]]: skills = list(capabilities.get("skills", []) or []) purpose = str(role.get("purpose", "")).strip() + # Form B: bind the role to an APM-deployed native agent. + agent_ref = role.get("agent_ref") + + if agent_ref: + if native and agent_ref not in native: + warnings.append( + f"Role '{role_id}': agent_ref '{agent_ref}' not found in " + f".opencode/agents/ ({', '.join(sorted(native)) or 'empty'}). " + "Run 'apm install' first?" + ) + preset[agent_ref] = { + **agent_fields, + "mcps": mcps, + "skills": skills, + } + if not purpose: + pass # persona comes from the APM primitive; nothing to do + else: + warnings.append( + f"Role '{role_id}': 'purpose' ignored - with agent_ref the " + "persona (prompt) comes from the APM agent primitive." + ) + continue + runtime_agent = role.get("runtime_agent", role_id) if runtime_agent == "custom": # Explicitly marked as custom agent -> use the role's own name. @@ -222,6 +257,20 @@ def build_preset(profile: dict, mapping: dict) -> tuple[dict, dict, list[str]]: "entry its model stays unchanged." ) + # Inline (non-agent_ref) roles colliding with deployed native agents + # would create OMOS custom agents that shadow the APM-deployed ones. + for role in profile["roles"]: + if role.get("agent_ref"): + continue + role_id = role.get("id") + runtime_agent = role.get("runtime_agent", role_id) + if runtime_agent == "custom" and native and role_id in native: + warnings.append( + f"Role '{role_id}' would create a custom agent shadowing the " + f"APM-deployed native agent '{role_id}'. Consider " + f"'agent_ref: {role_id}' instead." + ) + return preset, custom_agents, warnings @@ -393,7 +442,7 @@ def cmd_render(args: argparse.Namespace) -> int: profile, profile_path = load_team_profile(package_dir, args.team_file) mapping = load_mapping(Path(args.mapping) if args.mapping else None) - preset, custom_agents, warnings = build_preset(profile, mapping) + preset, custom_agents, warnings = build_preset(profile, mapping, package_dir) output = Path(args.output) if args.output else package_dir / DEFAULT_OUTPUT if output.exists(): @@ -430,6 +479,7 @@ def cmd_render(args: argparse.Namespace) -> int: "roles": { r.get("id"): { "runtimeAgent": r.get("runtime_agent", r.get("id")), + "agentRef": r.get("agent_ref"), "modelClass": r.get("model_class"), "resolvedModel": mapping[r["model_class"]], "mcps": (r.get("capabilities", {}) or {}).get("mcps", []), diff --git a/corentic-package-manager/examples/microsoft-design-review/team-profile.yaml b/corentic-package-manager/examples/microsoft-design-review/team-profile.yaml index 6b998bb..6a693ae 100644 --- a/corentic-package-manager/examples/microsoft-design-review/team-profile.yaml +++ b/corentic-package-manager/examples/microsoft-design-review/team-profile.yaml @@ -20,16 +20,10 @@ roles: skills: [] - id: reviewer - purpose: >- - Performs the design review following the design-review.prompt.md - template. Evaluates UI consistency against the - design-standards.instructions.md. Reports findings with severity, - no unauthorized code changes. - runtime_agent: oracle # deep review = oracle profile + # persona (prompt, identity) comes from the APM-deployed primitive + # .opencode/agents/design-reviewer.md - created by 'apm install' + agent_ref: design-reviewer model_class: high-reasoning - capabilities: - mcps: [] - skills: [style-checker] - id: style-checker purpose: >-