Просмотр исходного кода

refactor(canonical): retire the agent metadata sidecar

Make canonical oac blocks and content/profiles the sole authored metadata
source. Registry emission now owns profile component lists, preserves
registry-owned profile presentation metadata, and retires config:agent-metadata.

Remove legacy sidecar loading, auto-detect merging, install payloads, tests,
and active guidance. Keep eval-runner explicitly registry-carried because its
evaluation harness remains non-canonical. Regenerate both tool targets and
pin the change with sidecar-free, profile-emission, installer, and
fixed-point tests.
darrenhinde 2 недель назад
Родитель
Сommit
5c7d4cc840
53 измененных файлов с 695 добавлено и 2041 удалено
  1. 0 363
      .opencode/config/agent-metadata.json
  2. 34 539
      .opencode/context/openagents-repo/core-concepts/agent-metadata.md
  3. 27 179
      .opencode/context/openagents-repo/standards/agent-frontmatter.md
  4. 20 150
      .opencode/context/openagents-repo/standards/navigation.md
  5. 23 178
      .opencode/context/openagents-repo/standards/subagent-structure.md
  6. 0 10
      content/agents/subagents/code/build-agent.md
  7. 0 12
      content/agents/subagents/code/coder-agent.md
  8. 0 10
      content/agents/subagents/code/reviewer.md
  9. 0 8
      content/agents/subagents/code/test-engineer.md
  10. 0 20
      content/agents/subagents/core/context-manager.md
  11. 0 32
      content/agents/subagents/core/context-retriever.md
  12. 0 8
      content/agents/subagents/core/documentation.md
  13. 0 20
      content/agents/subagents/core/externalscout.md
  14. 0 8
      content/agents/subagents/development/devops-specialist.md
  15. 0 8
      content/agents/subagents/development/frontend-specialist.md
  16. 0 4
      content/agents/subagents/planning/adr-manager.md
  17. 0 4
      content/agents/subagents/planning/prioritization-engine.md
  18. 0 8
      content/agents/subagents/system-builder/context-organizer.md
  19. 0 8
      content/agents/subagents/system-builder/workflow-designer.md
  20. 0 1
      content/profiles/system/advanced.json
  21. 0 1
      content/profiles/system/business.json
  22. 0 1
      content/profiles/system/developer.json
  23. 0 1
      content/profiles/system/essential.json
  24. 0 1
      content/profiles/system/full.json
  25. 2 0
      docs/architecture/canonical-refactor/00-INDEX.md
  26. 2 0
      docs/architecture/canonical-refactor/01-feature-inventory.md
  27. 2 0
      docs/architecture/canonical-refactor/02-canonical-schema.md
  28. 2 0
      docs/architecture/canonical-refactor/03-adapter-specs.md
  29. 2 0
      docs/architecture/canonical-refactor/04-cli-build-distribution.md
  30. 2 0
      docs/architecture/canonical-refactor/05-impact-migration-tests.md
  31. 2 0
      docs/architecture/canonical-refactor/06-REVIEW.md
  32. 2 0
      docs/maintenance/REGISTRY_FIXES_SUMMARY.md
  33. 3 1
      docs/maintenance/REGISTRY_VALIDATION.md
  34. 1 2
      packages/compatibility-layer/src/adapters/OpenCodeAdapter.ts
  35. 13 56
      packages/compatibility-layer/src/core/AgentLoader.ts
  36. 2 2
      packages/compatibility-layer/src/core/ReferenceResolver.ts
  37. 130 32
      packages/compatibility-layer/src/core/RegistryEmitter.ts
  38. 4 4
      packages/compatibility-layer/src/index.ts
  39. 8 33
      packages/compatibility-layer/src/types.ts
  40. 132 0
      packages/compatibility-layer/tests/unit/build/registry-profile-emission.test.ts
  41. 3 4
      packages/compatibility-layer/tests/unit/build/system-profile-equivalence.test.ts
  42. 91 41
      packages/compatibility-layer/tests/unit/core/RegistryEmitter.test.ts
  43. 13 54
      packages/compatibility-layer/tests/unit/schema/canonical-agent.test.ts
  44. 88 54
      packages/compatibility-layer/tests/unit/types/OacBlock.test.ts
  45. 0 10
      plugins/claude-code/agents/code-reviewer.md
  46. 0 12
      plugins/claude-code/agents/coder-agent.md
  47. 0 20
      plugins/claude-code/agents/context-manager.md
  48. 0 19
      plugins/claude-code/agents/external-scout.md
  49. 0 8
      plugins/claude-code/agents/test-engineer.md
  50. 3 3
      scripts/README.md
  51. 6 39
      scripts/registry/auto-detect-components.sh
  52. 74 69
      scripts/tests/test-e2e-install.sh
  53. 4 4
      scripts/tests/test-non-interactive.sh

+ 0 - 363
.opencode/config/agent-metadata.json

@@ -1,363 +0,0 @@
-{
-  "$schema": "https://opencode.ai/schemas/agent-metadata.json",
-  "schema_version": "1.0.0",
-  "description": "Centralized metadata for OpenAgents Control agents. This file stores metadata that is not part of the OpenCode agent schema but is needed for registry management, installation, and documentation.",
-  "agents": {
-    "openagent": {
-      "id": "openagent",
-      "name": "OpenAgent",
-      "category": "core",
-      "type": "agent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["universal", "coordination", "primary"],
-      "dependencies": [
-        "subagent:task-manager",
-        "subagent:batch-executor",
-        "subagent:documentation",
-        "subagent:contextscout",
-        "subagent:externalscout",
-        "context:standards-code",
-        "context:standards-docs",
-        "context:standards-tests",
-        "context:review-ref",
-        "context:delegation-ref",
-        "context:external-libraries-workflow"
-      ]
-    },
-    "opencoder": {
-      "id": "opencoder",
-      "name": "OpenCoder",
-      "category": "core",
-      "type": "agent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["development", "coding", "implementation"],
-      "dependencies": [
-        "subagent:documentation",
-        "subagent:task-manager",
-        "subagent:batch-executor",
-        "subagent:coder-agent",
-        "subagent:tester",
-        "subagent:reviewer",
-        "subagent:build-agent",
-        "subagent:contextscout",
-        "subagent:externalscout",
-        "context:standards-code",
-        "context:task-delegation-basics",
-        "context:component-planning",
-        "context:external-libraries-workflow"
-      ]
-    },
-    "repo-manager": {
-      "id": "repo-manager",
-      "name": "Repo Manager",
-      "category": "meta",
-      "type": "agent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["repository", "management", "orchestration"],
-      "dependencies": [
-        "subagent:task-manager",
-        "subagent:contextscout",
-        "subagent:documentation",
-        "subagent:coder-agent",
-        "subagent:tester",
-        "subagent:reviewer",
-        "subagent:build-agent"
-      ]
-    },
-    "system-builder": {
-      "id": "system-builder",
-      "name": "System Builder",
-      "category": "meta",
-      "type": "agent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["system-generation", "architecture", "scaffolding"],
-      "dependencies": [
-        "subagent:agent-generator",
-        "subagent:command-creator",
-        "subagent:domain-analyzer",
-        "subagent:context-organizer",
-        "subagent:workflow-designer"
-      ]
-    },
-    "copywriter": {
-      "id": "copywriter",
-      "name": "Copywriter",
-      "category": "content",
-      "type": "agent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["content", "marketing", "writing"],
-      "dependencies": [
-        "context:standards-docs"
-      ]
-    },
-    "technical-writer": {
-      "id": "technical-writer",
-      "name": "Technical Writer",
-      "category": "content",
-      "type": "agent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["documentation", "technical", "writing"],
-      "dependencies": [
-        "context:standards-docs"
-      ]
-    },
-    "data-analyst": {
-      "id": "data-analyst",
-      "name": "Data Analyst",
-      "category": "data",
-      "type": "agent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["data", "analysis", "visualization"],
-      "dependencies": []
-    },
-    "eval-runner": {
-      "id": "eval-runner",
-      "name": "Eval Runner",
-      "category": "testing",
-      "type": "agent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["testing", "evaluation", "quality"],
-      "dependencies": [
-        "context:standards-tests"
-      ]
-    },
-    "task-manager": {
-      "id": "task-manager",
-      "name": "TaskManager",
-      "category": "subagents/core",
-      "type": "subagent",
-      "version": "2.0.0",
-      "author": "opencode",
-      "tags": ["task-breakdown", "planning", "coordination"],
-      "dependencies": [
-        "context:task-delegation-basics"
-      ]
-    },
-    "batch-executor": {
-      "id": "batch-executor",
-      "name": "BatchExecutor",
-      "category": "subagents/core",
-      "type": "subagent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["parallel-execution", "batch-management", "coordination"],
-      "dependencies": [
-        "subagent:coder-agent",
-        "subagent:task-manager"
-      ]
-    },
-    "documentation": {
-      "id": "documentation",
-      "name": "DocWriter",
-      "category": "subagents/core",
-      "type": "subagent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["documentation", "writing"],
-      "dependencies": [
-        "context:standards-docs"
-      ]
-    },
-    "contextscout": {
-      "id": "contextscout",
-      "name": "ContextScout",
-      "category": "subagents/core",
-      "type": "subagent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["context", "discovery", "search"],
-      "dependencies": []
-    },
-    "externalscout": {
-      "id": "externalscout",
-      "name": "ExternalScout",
-      "category": "subagents/core",
-      "type": "subagent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["external", "documentation", "search"],
-      "dependencies": []
-    },
-    "context-manager": {
-      "id": "context-manager",
-      "name": "ContextManager",
-      "category": "subagents/core",
-      "type": "subagent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["context", "management", "organization"],
-      "dependencies": []
-    },
-    "context-retriever": {
-      "id": "context-retriever",
-      "name": "Context Retriever",
-      "category": "subagents/core",
-      "type": "subagent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["context", "retrieval", "search"],
-      "dependencies": []
-    },
-    "coder-agent": {
-      "id": "coder-agent",
-      "name": "CoderAgent",
-      "category": "subagents/code",
-      "type": "subagent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["coding", "implementation"],
-      "dependencies": [
-        "context:standards-code"
-      ]
-    },
-    "tester": {
-      "id": "tester",
-      "name": "TestEngineer",
-      "category": "subagents/code",
-      "type": "subagent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["testing", "tdd", "quality"],
-      "dependencies": [
-        "context:standards-tests"
-      ]
-    },
-    "reviewer": {
-      "id": "reviewer",
-      "name": "CodeReviewer",
-      "category": "subagents/code",
-      "type": "subagent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["review", "security", "quality"],
-      "dependencies": [
-        "context:standards-code",
-        "context:review-ref"
-      ]
-    },
-    "build-agent": {
-      "id": "build-agent",
-      "name": "BuildAgent",
-      "category": "subagents/code",
-      "type": "subagent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["build", "validation", "type-checking"],
-      "dependencies": []
-    },
-    "frontend-specialist": {
-      "id": "frontend-specialist",
-      "name": "OpenFrontendSpecialist",
-      "category": "subagents/development",
-      "type": "subagent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["frontend", "ui", "design"],
-      "dependencies": [
-        "context:standards-code"
-      ]
-    },
-    "devops-specialist": {
-      "id": "devops-specialist",
-      "name": "OpenDevopsSpecialist",
-      "category": "subagents/development",
-      "type": "subagent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["devops", "ci-cd", "infrastructure"],
-      "dependencies": []
-    },
-    "agent-generator": {
-      "id": "agent-generator",
-      "name": "AgentGenerator",
-      "category": "subagents/system-builder",
-      "type": "subagent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["generation", "agents", "scaffolding"],
-      "dependencies": []
-    },
-    "command-creator": {
-      "id": "command-creator",
-      "name": "CommandCreator",
-      "category": "subagents/system-builder",
-      "type": "subagent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["commands", "generation", "scaffolding"],
-      "dependencies": []
-    },
-    "domain-analyzer": {
-      "id": "domain-analyzer",
-      "name": "DomainAnalyzer",
-      "category": "subagents/system-builder",
-      "type": "subagent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["analysis", "domain", "architecture"],
-      "dependencies": []
-    },
-    "context-organizer": {
-      "id": "context-organizer",
-      "name": "ContextOrganizer",
-      "category": "subagents/system-builder",
-      "type": "subagent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["context", "organization", "structure"],
-      "dependencies": []
-    },
-    "workflow-designer": {
-      "id": "workflow-designer",
-      "name": "WorkflowDesigner",
-      "category": "subagents/system-builder",
-      "type": "subagent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["workflow", "design", "architecture"],
-      "dependencies": []
-    },
-    "image-specialist": {
-      "id": "image-specialist",
-      "name": "Image Specialist",
-      "category": "subagents/utils",
-      "type": "subagent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["images", "editing", "generation"],
-      "dependencies": []
-    },
-    "simple-responder": {
-      "id": "simple-responder",
-      "name": "Simple Responder",
-      "category": "subagents/test",
-      "type": "subagent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["testing", "evaluation"],
-      "dependencies": []
-    }
-  },
-  "defaults": {
-    "agent": {
-      "version": "1.0.0",
-      "author": "opencode",
-      "type": "agent",
-      "tags": []
-    },
-    "subagent": {
-      "version": "1.0.0",
-      "author": "opencode",
-      "type": "subagent",
-      "tags": []
-    }
-  }
-}

+ 34 - 539
.opencode/context/openagents-repo/core-concepts/agent-metadata.md

@@ -1,559 +1,54 @@
-<!-- Context: openagents-repo/core-concepts/agent-metadata | Priority: critical | Version: 1.0 | Updated: 2026-01-31 -->
-# Core Concept: Agent Metadata System
+<!-- Context: openagents-repo/core-concepts/agent-metadata | Priority: critical | Version: 2.0 | Updated: 2026-09-02 -->
+# Core Concept: Agent Metadata
 
-**Purpose**: Understanding the centralized metadata system for OpenAgents Control  
-**Priority**: CRITICAL - Load this before working with agent metadata
+## Current metadata workflow
 
----
-
-## What Is the Agent Metadata System?
-
-The agent metadata system separates **OpenCode-compliant agent configuration** from **OpenAgents Control registry metadata**. This solves the problem of OpenCode validation errors when agents contain fields that aren't part of the OpenCode agent schema.
-
-**Key Principle**: Agent frontmatter contains ONLY valid OpenCode fields. All other metadata lives in a centralized file.
-
----
-
-## The Problem We Solved
-
-### Before (Validation Errors)
-
-Agent frontmatter contained fields that OpenCode doesn't recognize:
-
-```yaml
----
-id: opencoder                    # ❌ Not valid OpenCode field
-name: OpenCoder                  # ❌ Not valid OpenCode field
-category: core                   # ❌ Not valid OpenCode field
-type: core                       # ❌ Not valid OpenCode field
-version: 1.0.0                   # ❌ Not valid OpenCode field
-author: opencode                 # ❌ Not valid OpenCode field
-tags: [development, coding]      # ❌ Not valid OpenCode field
-dependencies: []              # ❌ Not valid OpenCode field
-description: "..."               # ✅ Valid OpenCode field
-mode: primary                    # ✅ Valid OpenCode field
-temperature: 0.1                 # ✅ Valid OpenCode field
-tools: {...}                     # ✅ Valid OpenCode field
-permission: {...}                # ✅ Valid OpenCode field
----
-```
-
-**Result**: OpenCode validation errors:
-```
-Extra inputs are not permitted, field: 'id', value: 'opencoder'
-Extra inputs are not permitted, field: 'category', value: 'core'
-Extra inputs are not permitted, field: 'type', value: 'core'
-... (9 validation errors)
-```
-
-### After (Clean Separation)
-
-**Agent frontmatter** (`.opencode/agent/core/opencoder.md`):
-```yaml
----
-# Metadata stored in: .opencode/config/agent-metadata.json
-description: "Orchestration agent for complex coding, architecture, and multi-file refactoring"
-mode: primary
-temperature: 0.1
-tools: {...}
-permission: {...}
----
-```
-
-**Centralized metadata** (`.opencode/config/agent-metadata.json`):
-```json
-{
-  "agents": {
-    "opencoder": {
-      "id": "opencoder",
-      "name": "OpenCoder",
-      "category": "core",
-      "type": "agent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["development", "coding", "implementation"],
-      "dependencies": [
-        "subagent:documentation",
-        "subagent:coder-agent",
-        "context:core/standards/code"
-      ]
-    }
-  }
-}
-```
-
-**Result**: ✅ No validation errors, clean separation of concerns
-
----
-
-## Valid OpenCode Agent Fields
-
-Based on [OpenCode documentation](https://opencode.ai/docs/agents/), these are the ONLY valid frontmatter fields:
-
-### Required Fields
-- `description` - When to use this agent (required)
-- `mode` - Agent type: `primary`, `subagent`, or `all` (defaults to `all`)
-
-### Optional Fields
-- `model` - Model override (e.g., `anthropic/claude-sonnet-4-20250514`)
-- `temperature` - Response randomness (0.0-1.0)
-- `maxSteps` - Max agentic iterations
-- `disable` - Set to `true` to disable agent
-- `prompt` - Custom prompt file path (e.g., `{file:./prompts/build.txt}`)
-- `hidden` - Hide from @ autocomplete (subagents only)
-- `tools` - Tool access configuration
-- `permission` - Permission rules for tools (v1.1.1+, replaces deprecated `permissions`)
-
-### Example Valid Frontmatter
+`content/agents/**` is the canonical source. Keep OpenCode configuration and OpenAgents Control metadata together in the agent's frontmatter:
 
 ```yaml
 ---
-description: "Code review agent with security focus"
+name: MyAgent
+description: "Does one focused task"
 mode: subagent
-model: anthropic/claude-sonnet-4-20250514
-temperature: 0.1
-tools:
-  read: true
-  grep: true
-  glob: true
-  write: false
-  edit: false
-permission:  # v1.1.1+ (singular, not plural)
-  bash:
-    "*": ask
-    "git *": allow
-  edit: deny
+oac:
+  id: my-agent
+  name: MyAgent
+  category: subagents/code
+  type: subagent
+  version: "1.0.0"
+  author: opencode
+  tags: [example]
+  dependencies: []
+  targets: [opencode]
 ---
 ```
 
----
-
-## Centralized Metadata File
+`oac:` is OpenAgents Control metadata. It is not an OpenCode field; the compatibility build reads it from canonical content and emits target-specific artifacts.
 
-**Location**: `.opencode/config/agent-metadata.json`
+## Registry generation
 
-### Schema
+Do not edit registry entries or a metadata sidecar. The RegistryEmitter reads canonical `content/agents/**` files and emits the registry during the compatibility build.
 
-```json
-{
-  "$schema": "https://opencode.ai/schemas/agent-metadata.json",
-  "schema_version": "1.0.0",
-  "description": "Centralized metadata for OpenAgents Control agents",
-  "agents": {
-    "agent-id": {
-      "id": "agent-id",
-      "name": "Agent Name",
-      "category": "core|development|content|data|product|learning|meta",
-      "type": "agent|subagent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["tag1", "tag2"],
-      "dependencies": [
-        "subagent:subagent-id",
-        "context:path/to/context"
-      ]
-    }
-  },
-  "defaults": {
-    "agent": {
-      "version": "1.0.0",
-      "author": "opencode",
-      "type": "agent",
-      "tags": []
-    },
-    "subagent": {
-      "version": "1.0.0",
-      "author": "opencode",
-      "type": "subagent",
-      "tags": []
-    }
-  }
-}
+```text
+content/agents/** (frontmatter + oac)
+  → RegistryEmitter
+  → generated registry and target outputs
 ```
 
-### Metadata Fields
+When changing an agent, edit its canonical file, then run the project's compatibility build and registry validation. Generated `.opencode/agent/**` and `plugins/**` files are outputs, not sources.
 
-| Field | Required | Description | Example |
-|-------|----------|-------------|---------|
-| `id` | Yes | Unique identifier (kebab-case) | `"opencoder"` |
-| `name` | Yes | Display name | `"OpenCoder"` |
-| `category` | Yes | Agent category | `"core"` |
-| `type` | Yes | Component type | `"agent"` or `"subagent"` |
-| `version` | Yes | Version number | `"1.0.0"` |
-| `author` | Yes | Author identifier | `"opencode"` |
-| `tags` | No | Discovery tags | `["development", "coding"]` |
-| `dependencies` | No | Component dependencies | `["subagent:tester"]` |
+## Metadata checklist
 
----
-
-## How It Works
-
-### 1. Agent Creation
+- Use a stable kebab-case `oac.id`.
+- Keep `oac.category`, `type`, `tags`, and `dependencies` with the agent.
+- Declare each target in `oac.targets`; put target-only settings in `oac.overrides`.
+- Do not merge metadata from a second source.
 
-When creating a new agent:
+## Eval runner exception
 
-**Step 1**: Create agent file with ONLY valid OpenCode fields
-
-```bash
-# Create agent file
-touch .opencode/agent/category/my-agent.md
-```
-
-```yaml
----
-description: "My agent description"
-mode: subagent
-temperature: 0.2
-tools:
-  read: true
-  write: true
----
-
-# Agent prompt content here
-```
-
-**Step 2**: Add metadata to `.opencode/config/agent-metadata.json`
-
-```json
-{
-  "agents": {
-    "my-agent": {
-      "id": "my-agent",
-      "name": "My Agent",
-      "category": "development",
-      "type": "subagent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": ["custom", "helper"],
-      "dependencies": ["context:core/standards/code"]
-    }
-  }
-}
-```
-
-**Step 3**: Run auto-detect to update registry
-
-```bash
-./scripts/registry/auto-detect-components.sh --auto-add
-```
-
-The auto-detect script:
-1. Reads frontmatter from agent file (description, mode, etc.)
-2. Reads metadata from `agent-metadata.json` (id, name, tags, dependencies)
-3. Merges both into registry.json entry
-
-### 2. Auto-Detect Integration
-
-The auto-detect script (`scripts/registry/auto-detect-components.sh`) has been enhanced to:
-
-1. **Extract frontmatter** - Read description from agent file
-2. **Lookup metadata** - Check `agent-metadata.json` for agent ID
-3. **Merge data** - Combine frontmatter + metadata
-4. **Update registry** - Write complete entry to registry.json
-
-**Code snippet** (from auto-detect script):
-
-```bash
-# Check if agent-metadata.json exists and merge metadata from it
-local metadata_file="$REPO_ROOT/.opencode/config/agent-metadata.json"
-if [ -f "$metadata_file" ] && command -v jq &> /dev/null; then
-    # Try to find metadata for this agent ID
-    local metadata_entry
-    metadata_entry=$(jq -r ".agents[\"$id\"] // empty" "$metadata_file" 2>/dev/null)
-    
-    if [ -n "$metadata_entry" ] && [ "$metadata_entry" != "null" ]; then
-        # Merge name, tags, dependencies from metadata
-        # ...
-    fi
-fi
-```
-
-### 3. Registry Output
-
-The registry.json entry contains merged data:
-
-```json
-{
-  "id": "opencoder",
-  "name": "OpenCoder",
-  "type": "agent",
-  "path": ".opencode/agent/core/opencoder.md",
-  "description": "Orchestration agent for complex coding...",
-  "category": "core",
-  "tags": ["development", "coding", "implementation"],
-  "dependencies": [
-    "subagent:documentation",
-    "subagent:coder-agent",
-    "context:core/standards/code"
-  ]
-}
-```
-
----
-
-## Workflow
-
-### Adding a New Agent
-
-```bash
-# 1. Create agent file (OpenCode-compliant frontmatter only)
-vim .opencode/agent/category/my-agent.md
-
-# 2. Add metadata entry
-vim .opencode/config/agent-metadata.json
-
-# 3. Update registry
-./scripts/registry/auto-detect-components.sh --auto-add
-
-# 4. Validate
-./scripts/registry/validate-registry.sh
-```
-
-### Updating Agent Metadata
-
-**To update OpenCode configuration** (tools, permissions, temperature):
-```bash
-# Edit agent file frontmatter
-vim .opencode/agent/category/my-agent.md
-```
-
-**To update registry metadata** (tags, dependencies, version):
-```bash
-# Edit metadata file
-vim .opencode/config/agent-metadata.json
-
-# Re-run auto-detect
-./scripts/registry/auto-detect-components.sh --auto-add
-```
+`.opencode/agent/eval-runner.md` is an intentionally registry-carried evaluation harness. It is not canonical agent content and is not a replacement for `oac:` metadata. Its exception does not restore the retired metadata sidecar or a sidecar-merge workflow.
 
-### Updating Dependencies
-
-**Add a dependency**:
-```json
-{
-  "agents": {
-    "my-agent": {
-      "dependencies": [
-        "subagent:tester",
-        "context:core/standards/code",
-        "subagent:new-dependency"  // ← Add here
-      ]
-    }
-  }
-}
-```
-
-Then run:
-```bash
-./scripts/registry/auto-detect-components.sh --auto-add
-./scripts/registry/validate-registry.sh
-```
-
----
-
-## Benefits
-
-### ✅ OpenCode Compliance
-- Agent frontmatter contains ONLY valid OpenCode fields
-- No validation errors from OpenCode
-- Agents work correctly with OpenCode CLI
-
-### ✅ Registry Compatibility
-- Registry still has all metadata (id, name, category, tags, dependencies)
-- Auto-detect script merges frontmatter + metadata
-- Backward compatible with existing tools
-
-### ✅ Single Source of Truth
-- Metadata centralized in one file
-- Easy to update dependencies across multiple agents
-- Clear separation: OpenCode config vs. registry metadata
-
-### ✅ Maintainability
-- Update dependencies in one place
-- Consistent metadata across all agents
-- Easy to add new metadata fields
-
-### ✅ Validation
-- OpenCode validates frontmatter (no extra fields)
-- Registry validator checks dependencies exist
-- Clear error messages when metadata is missing
-
----
-
-## Migration Guide
-
-### Migrating from permissions (plural) to permission (singular)
-
-**OpenCode v1.1.1+ Change**: The field name changed from `permissions:` (plural) to `permission:` (singular).
-
-**Before** (deprecated):
-```yaml
-permissions:
-  bash:
-    "*": "deny"
-```
-
-**After** (v1.1.1+):
-```yaml
-permission:
-  bash:
-    "*": "deny"
-```
-
-**Migration Steps**:
-1. Find all agents using `permissions:` (plural)
-   ```bash
-   grep -r "^permissions:" .opencode/agent/
-   ```
-
-2. Replace with `permission:` (singular) in each file
-
-3. Verify no validation errors:
-   ```bash
-   opencode agent validate
-   ```
-
-### Migrating Existing Agents
-
-**Step 1**: Identify agents with extra fields
-
-```bash
-# Find agents with invalid OpenCode fields
-grep -r "^id:\|^name:\|^category:\|^type:\|^version:\|^author:\|^tags:\|^dependencies:" .opencode/agent/
-```
-
-**Step 2**: Extract metadata to `agent-metadata.json`
-
-For each agent:
-1. Copy `id`, `name`, `category`, `type`, `version`, `author`, `tags`, `dependencies` to metadata file
-2. Remove these fields from agent frontmatter
-3. Keep ONLY valid OpenCode fields in frontmatter
-
-**Step 3**: Update registry
-
-```bash
-# Remove old entries
-jq 'del(.components.agents[] | select(.id == "agent-id"))' registry.json > tmp.json && mv tmp.json registry.json
-
-# Re-add with new metadata
-./scripts/registry/auto-detect-components.sh --auto-add
-```
-
-**Step 4**: Validate
-
-```bash
-./scripts/registry/validate-registry.sh
-```
-
----
-
-## Best Practices
-
-### Agent Frontmatter
-
-✅ **DO**:
-- Keep frontmatter minimal (only OpenCode fields)
-- Add comment pointing to metadata file
-- Use consistent formatting
-
-❌ **DON'T**:
-- Add custom fields to frontmatter
-- Duplicate metadata in both places
-- Skip the metadata file
-
-### Metadata File
-
-✅ **DO**:
-- Keep metadata file in version control
-- Update metadata when adding/removing dependencies
-- Use consistent naming (kebab-case for IDs)
-- Document why dependencies exist
-
-❌ **DON'T**:
-- Forget to update metadata when creating agents
-- Leave orphaned entries (agents that don't exist)
-- Skip validation after updates
-
-### Dependencies
-
-✅ **DO**:
-- Declare ALL dependencies (subagents, contexts)
-- Use correct format: `type:id`
-- Validate dependencies exist in registry
-
-❌ **DON'T**:
-- Reference components without declaring dependency
-- Use invalid dependency formats
-- Forget to update when dependencies change
-
----
-
-## Troubleshooting
-
-### OpenCode Validation Errors
-
-**Problem**: `Extra inputs are not permitted, field: 'id'`
-
-**Solution**: Remove invalid fields from agent frontmatter, add to metadata file
-
-```bash
-# 1. Edit agent file - remove id, name, category, type, version, author, tags, dependencies
-vim .opencode/agent/category/agent.md
-
-# 2. Add to metadata file
-vim .opencode/config/agent-metadata.json
-
-# 3. Update registry
-./scripts/registry/auto-detect-components.sh --auto-add
-```
-
-### Missing Metadata
-
-**Problem**: Auto-detect can't find metadata for agent
-
-**Solution**: Add entry to `agent-metadata.json`
-
-```json
-{
-  "agents": {
-    "agent-id": {
-      "id": "agent-id",
-      "name": "Agent Name",
-      "category": "core",
-      "type": "agent",
-      "version": "1.0.0",
-      "author": "opencode",
-      "tags": [],
-      "dependencies": []
-    }
-  }
-}
-```
-
-### Registry Out of Sync
-
-**Problem**: Registry has old metadata
-
-**Solution**: Remove entry and re-run auto-detect
-
-```bash
-# Remove old entry
-jq 'del(.components.agents[] | select(.id == "agent-id"))' registry.json > tmp.json && mv tmp.json registry.json
-
-# Re-add with current metadata
-./scripts/registry/auto-detect-components.sh --auto-add
-```
-
----
-
-## Related Files
-
-- **OpenCode Agent Docs**: https://opencode.ai/docs/agents/
-- **Registry System**: `.opencode/context/openagents-repo/core-concepts/registry.md`
-- **Adding Agents**: `.opencode/context/openagents-repo/guides/adding-agent-basics.md`
-- **Dependencies**: `.opencode/context/openagents-repo/quality/registry-dependencies.md`
-
----
+## Related
 
-**Last Updated**: 2026-01-31  
-**Version**: 1.0.0
+- [Agent frontmatter](../standards/agent-frontmatter.md)
+- [Subagent structure](../standards/subagent-structure.md)

+ 27 - 179
.opencode/context/openagents-repo/standards/agent-frontmatter.md

@@ -1,196 +1,44 @@
-<!-- Context: openagents-repo/standards/agent-frontmatter | Priority: critical | Version: 1.0 | Updated: 2026-01-31 -->
-# Standard: Agent YAML Frontmatter
+<!-- Context: openagents-repo/standards/agent-frontmatter | Priority: critical | Version: 2.0 | Updated: 2026-09-02 -->
+# Standard: Agent Frontmatter
 
-**Purpose**: Valid OpenCode agent frontmatter structure and common mistakes to avoid  
-**Priority**: CRITICAL - Load this before creating or modifying agent files
-
----
-
-## Core Principle
-
-Agent frontmatter must contain ONLY valid OpenCode fields. All other metadata (id, name, category, tags, dependencies) belongs in `.opencode/config/agent-metadata.json`.
-
-**Why**: OpenCode validates frontmatter strictly. Extra fields cause validation errors.
-
----
-
-## Valid OpenCode Fields
-
-### Required
-```yaml
----
-name: AgentName                      # Display name
-description: "What this agent does"  # When to use
-mode: subagent                       # primary, subagent, or all
----
-```
-
-### Optional
-```yaml
-temperature: 0.1                     # Response randomness (0.0-1.0)
-model: anthropic/claude-sonnet-4     # Model override
-maxSteps: 50                         # Max iterations
-disable: false                       # Disable agent
-hidden: false                        # Hide from autocomplete
-prompt: "{file:./prompts/custom.txt}" # Custom prompt
-
-tools:                               # Tool access
-  read: true
-  write: false
-  edit: false
-  bash: false
-  task: false
-
-permission:                          # Permission rules (v1.1.1+)
-  "*": "ask"                         # Catch-all (last-match-wins)
-  read: "allow"                      # Specific override
-  bash:
-    "*": "deny"
-    "git status*": "allow"
-  edit:
-    "**/*.env*": "deny"
-  task:
-    contextscout: "allow"
-    "*": "deny"
-
-skills:                              # Skills to load
-  - task-management
-```
-
----
-
-## Complete Example
+Canonical agent files live in `content/agents/**`. Their frontmatter combines valid OpenCode configuration with an `oac:` block for OpenAgents Control metadata.
 
 ```yaml
 ---
 name: TestEngineer
-description: Test authoring and TDD agent
+description: "Test authoring and TDD agent"
 mode: subagent
 temperature: 0.1
-tools:
-  read: true
-  grep: true
-  edit: true
-  write: true
-  bash: true
-  task: true
 permission:
   bash:
-    "npx vitest *": "allow"
-    "pytest *": "allow"
-    "sudo *": "deny"
-    "*": "deny"
-  edit:
-    "**/*.env*": "deny"
-  task:
-    contextscout: "allow"
-    "*": "deny"
+    "npm test *": allow
+oac:
+  id: tester
+  name: TestEngineer
+  category: subagents/code
+  type: subagent
+  version: "1.0.0"
+  author: opencode
+  tags: [testing, tdd]
+  dependencies: [context:standards-tests]
+  targets: [opencode, claude-code]
 ---
 ```
 
----
+## Rules
 
-## Common Mistakes (Fixed in 18 Agents)
+- Put OpenCode fields (`name`, `description`, `mode`, `tools`, `permission`, and related options) at the frontmatter root.
+- Put registry metadata only in `oac:`.
+- Do not duplicate metadata in another file or add an auto-detect merge step.
+- Use `permission` (singular), never duplicate YAML keys, and use one frontmatter block.
 
-### 1. Duplicate Keys ❌
-```yaml
-tools:
-  read: true
-  read: {"**/*": "allow"}  # ❌ Duplicate key
-```
-**Fix**: Use only one declaration per key
+## Build flow
 
-### 2. Orphaned List Items ❌
-```yaml
-tools:
-  read: true
-  - write: false  # ❌ No parent key
-```
-**Fix**: Proper YAML structure (no orphaned items)
+The RegistryEmitter consumes `content/agents/**` and emits registry and target artifacts. Edit canonical content, then regenerate and validate; never edit generated `.opencode/agent/**` or plugin outputs.
 
-### 3. Wrong Field Names ❌
-```yaml
-permissions:  # ❌ Deprecated - use 'permission' (singular)
-  bash:
-    "*": "deny"
-```
-**Fix**: Use correct field name `permission:` (singular, v1.1.1+)
-
-### 4. Extra Delimiter Blocks ❌
-```yaml
----
-name: MyAgent
----
-# Content
----  # ❌ Extra delimiter
-More content
-```
-**Fix**: Only one `---` block at top
-
-### 5. Invalid OpenCode Fields ❌
-```yaml
----
-id: my-agent          # ❌ Not valid
-category: development # ❌ Not valid
-type: agent           # ❌ Not valid
-version: 1.0.0        # ❌ Not valid
-tags: [coding]        # ❌ Not valid
-dependencies: []      # ❌ Not valid
-```
-**Fix**: Move to `.opencode/config/agent-metadata.json`:
-```json
-{
-  "agents": {
-    "my-agent": {
-      "id": "my-agent",
-      "category": "development",
-      "type": "agent",
-      "version": "1.0.0",
-      "tags": ["coding"],
-      "dependencies": []
-    }
-  }
-}
-```
-
----
-
-## Validation Checklist
-
-- [ ] Only valid OpenCode fields?
-- [ ] No duplicate keys?
-- [ ] No orphaned list items?
-- [ ] Correct field names (`permission` not `permissions`)?
-- [ ] Only one `---` delimiter at top?
-- [ ] Metadata moved to agent-metadata.json?
-- [ ] Valid YAML syntax?
-
----
-
-## Validation Commands
-
-```bash
-# Check YAML syntax
-yq eval '.opencode/agent/category/agent.md' > /dev/null
-
-# Find duplicate keys
-grep -A 50 "^---$" agent.md | grep -E "^[a-z_]+:" | sort | uniq -d
-
-# List all frontmatter keys
-grep -A 50 "^---$" agent.md | grep -E "^[a-z_]+:" | cut -d: -f1
-```
-
-**Valid keys**: `name`, `description`, `mode`, `temperature`, `model`, `maxSteps`, `disable`, `hidden`, `prompt`, `tools`, `permission`, `skills`
-
----
-
-## Related
-
-- **Agent Metadata**: `core-concepts/agent-metadata.md`
-- **Subagent Structure**: `standards/subagent-structure.md`
-- **Adding Agents**: `guides/adding-agent.md`
-- **OpenCode Docs**: https://opencode.ai/docs/agents/
-
----
+## Checklist
 
-**Last Updated**: 2026-01-31 | **Version**: 1.0.0
+- [ ] Valid YAML and OpenCode root fields
+- [ ] Complete `oac:` identity, category, type, version, author, targets
+- [ ] Dependencies declared in `oac.dependencies`
+- [ ] Compatibility build and registry validation run

+ 20 - 150
.opencode/context/openagents-repo/standards/navigation.md

@@ -1,159 +1,29 @@
-<!-- Context: openagents-repo/standards/navigation | Priority: critical | Version: 1.0 | Updated: 2026-01-31 -->
+<!-- Context: openagents-repo/standards/navigation | Priority: critical | Version: 2.0 | Updated: 2026-09-02 -->
 # OpenAgents Repo Standards
 
-**Purpose**: Standards for creating and maintaining agents in OpenAgents Control  
-**Last Updated**: 2026-01-31
+## Agent authoring
 
----
+1. Edit the canonical agent under `content/agents/**`.
+2. Keep OpenCode settings at the frontmatter root and OpenAgents Control metadata in `oac:`.
+3. Regenerate artifacts through the compatibility build; RegistryEmitter creates the registry from canonical content.
+4. Validate the emitted registry.
 
-## Overview
+Do not edit generated `.opencode/agent/**`, `plugins/**`, registry output, or a metadata sidecar.
 
-This directory contains standards for agent creation, focusing on:
-- Valid OpenCode YAML frontmatter structure
-- Subagent file organization and patterns
-- Common mistakes and how to avoid them
+## Standards
 
-These standards emerged from fixing YAML frontmatter issues across 18 agent files.
+| File | Use |
+|---|---|
+| [agent-frontmatter.md](agent-frontmatter.md) | Canonical frontmatter and `oac:` schema |
+| [subagent-structure.md](subagent-structure.md) | Prompt structure and canonical location |
+| [agent metadata](../core-concepts/agent-metadata.md) | Metadata and RegistryEmitter flow |
 
----
+## Quick example
 
-## Standards Files
-
-| File | Description | Priority | Lines |
-|------|-------------|----------|-------|
-| [agent-frontmatter.md](agent-frontmatter.md) | Valid OpenCode frontmatter fields and common mistakes | critical | <200 |
-| [subagent-structure.md](subagent-structure.md) | Standard structure for subagent files | critical | <200 |
-
----
-
-## Quick Reference
-
-### Valid OpenCode Fields (Frontmatter)
-
-**Required**:
-- `name` - Display name
-- `description` - When to use this agent
-- `mode` - Agent type (primary, subagent, all)
-
-**Optional**:
-- `temperature` - Response randomness (0.0-1.0)
-- `model` - Model override
-- `maxSteps` - Max iterations
-- `disable` - Disable agent
-- `hidden` - Hide from autocomplete
-- `prompt` - Custom prompt file
-- `tools` - Tool access config
-- `permission` - Permission rules
-- `skills` - Skills to load
-
-### Invalid Fields (Move to agent-metadata.json)
-
-These fields are NOT valid in OpenCode frontmatter:
-- `id` - Agent identifier
-- `category` - Agent category
-- `type` - Component type
-- `version` - Version number
-- `author` - Author identifier
-- `tags` - Discovery tags
-- `dependencies` - Component dependencies
-
-**Solution**: Move these to `.opencode/config/agent-metadata.json`
-
----
-
-## Common Mistakes Fixed
-
-### 1. Duplicate YAML Keys
-**Problem**: `read: true` followed by `read: {"**/*": "allow"}`  
-**Fix**: Use only one declaration per key
-
-### 2. Orphaned List Items
-**Problem**: Lines without parent keys (e.g., `- write: false`)  
-**Fix**: Proper YAML structure with parent keys
-
-### 3. Wrong Field Names
-**Problem**: `permission:` instead of `permissions:`  
-**Fix**: Use correct field name `permissions:`
-
-### 4. Extra Delimiter Blocks
-**Problem**: Multiple `---` blocks in content  
-**Fix**: Only one frontmatter block at top
-
-### 5. Invalid OpenCode Fields
-**Problem**: Fields like `id`, `category`, `type` in frontmatter  
-**Fix**: Move to agent-metadata.json
-
----
-
-## Loading Strategy
-
-### For Creating New Agents
-1. Load `agent-frontmatter.md` - Understand valid fields
-2. Load `subagent-structure.md` - Follow structure pattern
-3. Reference existing agents as examples
-4. Validate YAML syntax before committing
-
-### For Fixing Existing Agents
-1. Load `agent-frontmatter.md` - Identify invalid fields
-2. Check for common mistakes (duplicates, orphans, wrong names)
-3. Move invalid fields to agent-metadata.json
-4. Validate with YAML parser
-
-### For Code Reviews
-1. Load `agent-frontmatter.md` - Check frontmatter validity
-2. Load `subagent-structure.md` - Verify structure compliance
-3. Check for common mistakes
-4. Validate YAML syntax
-
----
-
-## Validation Commands
-
-### Check YAML Syntax
-```bash
-yq eval '.opencode/agent/category/agent.md' > /dev/null
-```
-
-### Check for Duplicate Keys
-```bash
-grep -A 50 "^---$" .opencode/agent/category/agent.md | grep -E "^[a-z_]+:" | sort | uniq -d
-```
-
-### List All Frontmatter Keys
-```bash
-grep -A 50 "^---$" .opencode/agent/category/agent.md | grep -E "^[a-z_]+:" | cut -d: -f1
+```yaml
+oac:
+  id: my-agent
+  category: subagents/core
+  type: subagent
+  targets: [opencode]
 ```
-
-Valid keys: `name`, `description`, `mode`, `temperature`, `model`, `maxSteps`, `disable`, `hidden`, `prompt`, `tools`, `permissions`, `skills`
-
----
-
-## Related Context
-
-### Core Concepts
-- [Agent Metadata System](../core-concepts/agent-metadata.md) - Centralized metadata management
-- [Agents](../core-concepts/AGENTS.md) - Agent system overview
-- [Categories](../core-concepts/categories.md) - Agent categorization
-
-### Guides
-- [Adding Agents](../guides/adding-agent-basics.md) - How to create new agents
-- [Testing Agents](../guides/testing-agent.md) - Agent testing workflow
-
-### Lookup
-- [File Locations](../lookup/file-locations.md) - Where files go
-
----
-
-## History
-
-**2026-01-31**: Initial standards created after fixing YAML frontmatter issues in 18 agent files. Issues fixed:
-- Duplicate YAML keys (e.g., `read: true` + `read: {"**/*": "allow"}`)
-- Orphaned list items (lines without parent keys)
-- Wrong field names (`permission:` → `permissions:`)
-- Extra `---` delimiter blocks in content
-- Invalid OpenCode fields in frontmatter
-
----
-
-**Version**: 1.0.0  
-**Maintainer**: OpenAgents Control Team

+ 23 - 178
.opencode/context/openagents-repo/standards/subagent-structure.md

@@ -1,192 +1,37 @@
-<!-- Context: openagents-repo/standards/subagent-structure | Priority: critical | Version: 1.0 | Updated: 2026-01-31 -->
+<!-- Context: openagents-repo/standards/subagent-structure | Priority: critical | Version: 2.0 | Updated: 2026-09-02 -->
 # Standard: Subagent File Structure
 
-**Purpose**: Standard structure for subagent files  
-**Priority**: CRITICAL - Load this before creating subagent files
-
----
-
-## File Template
+Create and edit subagents in `content/agents/subagents/{category}/`. This canonical source contains both the prompt and its metadata.
 
 ```markdown
 ---
-name: AgentName
-description: Brief description
+name: MyAgent
+description: "Brief description"
 mode: subagent
-temperature: 0.1
-tools: {...}
-permissions: {...}
----
-
-# AgentName
-> **Mission**: One-sentence mission
-
-<rule id="rule_name">Rule description</rule>
-
-<context>
-  <system>Role in pipeline</system>
-  <domain>Expertise area</domain>
-  <task>What agent does</task>
-  <constraints>Limitations</constraints>
-</context>
-
-<tier level="1" desc="Critical">
-  - @rule_id: Description
-</tier>
-
-## Workflow
-### Step 1: Preparation
-### Step 2: Execution
-### Step 3: Output
-
-## Output Format
-```yaml
-status: "success" | "failure"
-```
-```
-
----
-
-## Section Details
-
-### 1. Frontmatter
-- ONLY valid OpenCode fields (see agent-frontmatter.md)
-- No duplicate keys, orphaned items, or invalid fields
-
-### 2. Header + Mission
-```markdown
-# TestEngineer
-> **Mission**: Author tests following TDD — grounded in project standards.
-```
-
-### 3. Critical Rules (3-5 max)
-```markdown
-<rule id="context_first">ALWAYS call ContextScout BEFORE writing tests.</rule>
-<rule id="positive_and_negative">EVERY behavior needs positive AND negative tests.</rule>
-```
-
-### 4. Context
-```markdown
-<context>
-  <system>Code quality gate</system>
-  <domain>Code review, security, quality</domain>
-  <task>Review code against standards</task>
-  <constraints>Read-only, no modifications</constraints>
-</context>
-```
-
-### 5. Execution Tiers
-```markdown
-<tier level="1" desc="Critical">
-  - @context_first: Load context first
-</tier>
-<tier level="2" desc="Core">
-  - Load standards
-  - Analyze code
-</tier>
-<conflict_resolution>Tier 1 overrides Tier 2/3</conflict_resolution>
-```
-
----
-
-## Tool Permission Patterns
-
-### Read-Only (Reviewers, Analyzers)
-```yaml
-tools: {read: true, grep: true, glob: true, bash: false, edit: false, write: false}
-permissions:
-  bash: {"*": "deny"}
-  edit: {"**/*": "deny"}
-  task: {contextscout: "allow", "*": "deny"}
-```
-
-### Write-Enabled (Coders, Testers)
-```yaml
-tools: {read: true, edit: true, write: true, bash: true}
-permissions:
-  bash: {"npm test *": "allow", "git *": "allow", "sudo *": "deny", "*": "deny"}
-  edit: {"**/*.env*": "deny", "**/*.key": "deny"}
-  task: {contextscout: "allow", "*": "deny"}
-```
-
-### Restricted Bash (Task Managers)
-```yaml
-tools: {read: true, bash: true}
-permissions:
-  bash: {"npx ts-node*task-cli*": "allow", "mkdir -p .tmp/tasks*": "allow", "*": "deny"}
-```
-
+oac:
+  id: my-agent
+  name: MyAgent
+  category: subagents/core
+  type: subagent
+  version: "1.0.0"
+  author: opencode
+  tags: [example]
+  dependencies: []
+  targets: [opencode]
 ---
 
-## File Organization
-
+# MyAgent
+> **Mission**: One focused outcome.
 ```
-.opencode/agent/subagents/
-├── code/           # tester, reviewer, coder-agent, build-agent
-├── core/           # task-manager, contextscout, documentation
-├── system-builder/ # agent-generator, command-creator
-└── utils/          # image-specialist
-```
-
----
-
-## Validation Checklist
 
-- [ ] Valid OpenCode frontmatter (no extra fields)?
-- [ ] Mission statement present?
-- [ ] 3-5 critical rules with unique IDs?
-- [ ] Context section complete?
-- [ ] Execution tiers defined with conflict resolution?
-- [ ] Workflow steps clear and actionable?
-- [ ] Output format specified?
-- [ ] Tool permissions appropriate for role?
-- [ ] File in correct category directory?
-- [ ] No YAML syntax errors?
+## Required structure
 
----
-
-## Common Patterns
+1. Valid OpenCode root fields and one `oac:` block.
+2. A mission, critical rules, context, workflow, and output expectations.
+3. Appropriate tool permissions and target overrides when needed.
 
-**Context-First Pattern**:
-```markdown
-<rule id="context_first">
-  ALWAYS call ContextScout BEFORE starting work. Load relevant standards first.
-</rule>
-```
+## Generation
 
-**Read-Only Pattern**:
-```markdown
-<rule id="read_only">
-  Read-only agent. NEVER use write, edit, or bash. Provide suggestions only.
-</rule>
-```
-
-**Security Pattern**:
-```markdown
-permissions:
-  edit:
-    "**/*.env*": "deny"
-    "**/*.key": "deny"
-    "**/*.secret": "deny"
-```
-
----
-
-## Examples
-
-**See existing subagents**:
-- `.opencode/agent/subagents/code/tester.md` - Write-enabled with tests
-- `.opencode/agent/subagents/code/reviewer.md` - Read-only reviewer
-- `.opencode/agent/subagents/core/task-manager.md` - Restricted bash
-
----
-
-## Related
-
-- **Frontmatter**: `standards/agent-frontmatter.md`
-- **Metadata**: `core-concepts/agent-metadata.md`
-- **Adding Agents**: `guides/adding-agent.md`
-
----
+RegistryEmitter reads canonical content and emits the registry and target-specific files. Do not edit generated `.opencode/agent/**` or `plugins/**` files, and do not maintain metadata in a sidecar.
 
-**Last Updated**: 2026-01-31 | **Version**: 1.0.0
+See [agent frontmatter](agent-frontmatter.md) for field details.

+ 0 - 10
content/agents/subagents/code/build-agent.md

@@ -104,12 +104,6 @@ task(subagent_type="ContextScout", description="Find build standards", prompt="F
 3. **Apply** any custom build configurations or strictness requirements
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
----
-
 ## What NOT to Do
 
 - ❌ **Don't skip ContextScout** — build validation without project standards = running wrong commands
@@ -120,10 +114,6 @@ task(subagent_type="ContextScout", description="Find build standards", prompt="F
 - ❌ **Don't give vague error reports** — include file paths, line numbers, and what's expected
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
   <context_first>ContextScout before any validation — understand project conventions first</context_first>
   <detect_first>Language detection before any commands — never assume</detect_first>
   <read_only>Report errors, never fix them — clear separation of concerns</read_only>

+ 0 - 12
content/agents/subagents/code/coder-agent.md

@@ -120,12 +120,6 @@ task(subagent_type="ContextScout", description="Find coding standards for [featu
 3. If ContextScout flags a framework/library → call **ExternalScout** for live docs (see below)
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
----
-
 ## Workflow
 
 ### Step 1: Read Subtask JSON
@@ -273,12 +267,6 @@ Summary: Implemented JWT authentication with refresh tokens and error handling
 - Status marking is the signal that enables parallel workflow progression
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
----
-
 ## Principles
 
 - Context first, code second. Always.

+ 0 - 10
content/agents/subagents/code/reviewer.md

@@ -109,12 +109,6 @@ task(subagent_type="ContextScout", description="Find code review standards", pro
 3. Flag deviations from team standards as findings
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
----
-
 ## What NOT to Do
 
 - ❌ **Don't skip ContextScout** — reviewing without project standards = generic feedback that misses project-specific issues
@@ -125,10 +119,6 @@ task(subagent_type="ContextScout", description="Find code review standards", pro
 - ❌ **Don't skip error handling checks** — missing error handling is a correctness issue
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
   <context_first>ContextScout before any review — standards-blind reviews are useless</context_first>
   <security_first>Security findings always surface first — they have the highest impact</security_first>
   <read_only>Suggest, never apply — the developer owns the fix</read_only>

+ 0 - 8
content/agents/subagents/code/test-engineer.md

@@ -125,10 +125,6 @@ task(subagent_type="ContextScout", description="Find testing standards", prompt=
 3. Structure your test plan to match project conventions
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
    - ✅ Positive: [expected success outcome]
    - ❌ Negative: [expected failure/edge case handling]
    - ✅ Positive: [expected success outcome]
@@ -146,10 +142,6 @@ task(subagent_type="ContextScout", description="Find testing standards", prompt=
 - ❌ **Don't skip the test plan** — propose before implementing, get approval
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
   <context_first>ContextScout before any test writing — conventions matter</context_first>
   <tdd_mindset>Think about testability before implementation — tests define behavior</tdd_mindset>
   <deterministic>Tests must be reliable — no flakiness, no external dependencies</deterministic>

+ 0 - 20
content/agents/subagents/core/context-manager.md

@@ -124,10 +124,6 @@ oac:
 <task>Discover context structure via navigation → catalog existing context → validate integrity → propose improvements → maintain lifecycle</task>
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 ## 📋 Process Flow
@@ -258,10 +254,6 @@ oac:
 </process_flow>
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
   <parameter name="request_type" type="enum">
     Type of context management request:
     - "discover": Discover and map context structure
@@ -388,10 +380,6 @@ oac:
 </output_specification>
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
   <pre_execution>
     - Verify request_type is valid
     - Verify scope exists or is "all"
@@ -450,10 +438,6 @@ oac:
 </context_management_principles>
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 ## 📝 Common Operations
@@ -494,10 +478,6 @@ Details: Overall context health and maintenance recommendations
 ```
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 **ContextManager** - Organize, validate, and maintain your project context!

+ 0 - 32
content/agents/subagents/core/context-retriever.md

@@ -296,10 +296,6 @@ Always structure your response in this format:
 **Files Searched**: {number of files examined}
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 ### 🎯 Primary Results (Must Read)
@@ -322,10 +318,6 @@ Always structure your response in this format:
 - {what to do with this information}
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 ### 📚 Secondary Results (Should Read)
@@ -341,10 +333,6 @@ Always structure your response in this format:
 **Why Read This**: {brief explanation of value}
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 ## 📋 Summary
@@ -404,10 +392,6 @@ read(filePath="{discovered-standards-file}")
 **Files Searched**: 12
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
     └── best-practices.md ⭐ FOUND
 ---
 
@@ -436,10 +420,6 @@ read(filePath="{discovered-standards-file}")
 - Follow naming conventions for consistency
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 ### 📍 Context Structure Discovered
@@ -460,10 +440,6 @@ docs/contributing/
 ```
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 ### 📋 Summary
@@ -611,20 +587,12 @@ Repository lacks structured context
 **Files Searched**: 0
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 ### 📍 Context Structure Discovered
 {show structure}
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 ### 📍 Many Relevant Files Found ({count})

+ 0 - 8
content/agents/subagents/core/documentation.md

@@ -97,10 +97,6 @@ task(subagent_type="ContextScout", description="Find documentation standards", p
 3. **Apply** formatting, structure, and tone standards to your writing
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 ## What NOT to Do
@@ -113,10 +109,6 @@ task(subagent_type="ContextScout", description="Find documentation standards", p
 - ❌ **Don't ignore existing style** — match what's already there
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
   <context_first>ContextScout before any writing — consistency requires knowing the standards</context_first>
   <propose_first>Always propose before writing — documentation changes need sign-off</propose_first>
   <concise>Scannable in <30 seconds — if not, it's too long</concise>

+ 0 - 20
content/agents/subagents/core/externalscout.md

@@ -111,10 +111,6 @@ oac:
 </critical_rules>
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
   <tier level="1" desc="Critical Operations">
     - @check_cache_first: Check .tmp/external-context/ before fetching
     - @tool_usage: Use ONLY allowed tools
@@ -273,10 +269,6 @@ oac:
 </workflow_execution>
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 ## Quick Reference
@@ -286,10 +278,6 @@ oac:
 **Supported Libraries**: Drizzle | Prisma | Better Auth | NextAuth.js | Clerk | Next.js | React | TanStack Query/Router | Cloudflare Workers | AWS Lambda | Vercel | Shadcn/ui | Radix UI | Tailwind CSS | Zustand | Jotai | Zod | React Hook Form | Vitest | Playwright
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
     ├── cloudflare-deployment.md
     ├── server-functions.md
     └── file-routing.md
@@ -328,10 +316,6 @@ If Context7 API fails:
 3. Suggest checking `.opencode/context/` for cached docs
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 ## Success Criteria
@@ -351,7 +335,3 @@ You succeed when ALL of these are complete:
 - Return summary without file locations
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-

+ 0 - 8
content/agents/subagents/development/devops-specialist.md

@@ -106,10 +106,6 @@ task(subagent_type="ContextScout", description="Find DevOps standards", prompt="
 3. If ContextScout flags a cloud service or tool → verify current docs before implementing
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 ## What NOT to Do
@@ -123,10 +119,6 @@ task(subagent_type="ContextScout", description="Find DevOps standards", prompt="
 - ❌ **Don't ignore peer dependencies** — verify version compatibility before deploying
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
   <pre_flight>
     - ContextScout called and standards loaded
     - Parent agent requirements clear

+ 0 - 8
content/agents/subagents/development/frontend-specialist.md

@@ -101,10 +101,6 @@ task(subagent_type="ContextScout", description="Find frontend design standards",
 3. If ContextScout flags a UI library (Tailwind, Shadcn, etc.) → call **ExternalScout** (see below)
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 ## Workflow
@@ -157,10 +153,6 @@ task(subagent_type="ContextScout", description="Find frontend design standards",
 4. Present: "Updated design saved. Previous version preserved."
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 <heuristics>

+ 0 - 4
content/agents/subagents/planning/adr-manager.md

@@ -100,10 +100,6 @@ task(subagent_type="ContextScout", description="Find ADR standards", prompt="Fin
 3. **Apply** formatting, structure, and linking standards to your ADRs
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 ## Workflow

+ 0 - 4
content/agents/subagents/planning/prioritization-engine.md

@@ -100,10 +100,6 @@ task(subagent_type="ContextScout", description="Find prioritization context for
 3. **Apply** those criteria to RICE and WSJF scoring
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 ## Workflow

+ 0 - 8
content/agents/subagents/system-builder/context-organizer.md

@@ -95,10 +95,6 @@ task(subagent_type="ContextScout", description="Find context system standards",
 3. **Apply** MVI format, frontmatter, and structure standards to all generated files
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 ## What NOT to Do
@@ -112,10 +108,6 @@ task(subagent_type="ContextScout", description="Find context system standards",
 - ❌ **Don't skip navigation.md** — every category needs one
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
   <!-- Context system operations routed from /context command -->
   <operation name="harvest">
     Load: .opencode/context/core/context-system/operations/harvest.md

+ 0 - 8
content/agents/subagents/system-builder/workflow-designer.md

@@ -94,10 +94,6 @@ task(subagent_type="ContextScout", description="Find workflow design standards",
 3. **Apply** validation gate, context dependency, and success criteria standards
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 ## What NOT to Do
@@ -110,10 +106,6 @@ task(subagent_type="ContextScout", description="Find workflow design standards",
 - ❌ **Don't ignore complexity patterns** — match the pattern to the use case complexity
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
   <simple_pattern>
     Linear execution with validation:
     1. Validate inputs → 2. Execute main task → 3. Validate outputs → 4. Deliver results

+ 0 - 1
content/profiles/system/advanced.json

@@ -55,7 +55,6 @@
     "notify"
   ],
   "config": [
-    "agent-metadata",
     "env-example",
     "readme"
   ]

+ 0 - 1
content/profiles/system/business.json

@@ -31,7 +31,6 @@
     "notify"
   ],
   "config": [
-    "agent-metadata",
     "env-example",
     "readme"
   ]

+ 0 - 1
content/profiles/system/developer.json

@@ -35,7 +35,6 @@
   ],
   "plugins": [],
   "config": [
-    "agent-metadata",
     "env-example",
     "readme"
   ]

+ 0 - 1
content/profiles/system/essential.json

@@ -23,7 +23,6 @@
   ],
   "plugins": [],
   "config": [
-    "agent-metadata",
     "env-example"
   ]
 }

+ 0 - 1
content/profiles/system/full.json

@@ -45,7 +45,6 @@
     "notify"
   ],
   "config": [
-    "agent-metadata",
     "env-example",
     "readme"
   ]

+ 2 - 0
docs/architecture/canonical-refactor/00-INDEX.md

@@ -1,5 +1,7 @@
 # OAC Canonical Refactor — Master Spec Index
 
+> **Current state (2026-09-02):** This is historical refactor documentation. The active metadata workflow is canonical `content/agents/**` `oac:` blocks emitted by RegistryEmitter; sidecar metadata and sidecar merging are superseded.
+
 > **Status:** Specification — v2 (post spec-pass reconciliation). No implementation yet.
 > **Goal:** Re-architect OAC from an OpenCode-specific project into a tool-agnostic,
 > npm-distributed system with a single neutral source of truth that generates

+ 2 - 0
docs/architecture/canonical-refactor/01-feature-inventory.md

@@ -1,5 +1,7 @@
 # 01 — Feature & Content Inventory
 
+> **Current state (2026-09-02):** This inventory records the pre-migration architecture. The active metadata workflow is canonical `content/agents/**` `oac:` blocks emitted by RegistryEmitter; sidecar metadata and sidecar merging are superseded.
+
 > **Owner:** Agent A
 > **Status:** Spec only — no implementation. **v2** (2026-07-15): census re-verified from
 > disk; `{pattern, effect}` terminology aligned to Locked Decision #5's `{scope, decision}`;

+ 2 - 0
docs/architecture/canonical-refactor/02-canonical-schema.md

@@ -1,5 +1,7 @@
 # 02 — Canonical IR Schema — **v3**
 
+> **Current state (2026-09-02):** This schema analysis is historical. The active metadata workflow is canonical `content/agents/**` `oac:` blocks emitted by RegistryEmitter; sidecar metadata and sidecar merging are superseded.
+
 > **Status:** Specification only — v3 (post `06-REVIEW.md` repairs). No implementation.
 > Zod blocks are *illustrative*: they define the target shape of `packages/core/src/schema/*.ts`,
 > not code to ship as-is.

+ 2 - 0
docs/architecture/canonical-refactor/03-adapter-specs.md

@@ -1,5 +1,7 @@
 # 03 — Adapter & Transform Specs (Agent C) — **v2**
 
+> **Current state (2026-09-02):** This adapter specification is historical. The active metadata workflow is canonical `content/agents/**` `oac:` blocks emitted by RegistryEmitter; sidecar metadata and sidecar merging are superseded. `eval-runner.md` remains an intentionally registry-carried, non-canonical evaluation harness—not a sidecar replacement.
+
 > **Status:** Specification only — no implementation code. **v2** (realigned to Option A
 > permissions + MVI context metadata, per `00-INDEX.md` v2).
 > **Scope:** For each of the 4 build targets (OpenCode, Claude Code, Cursor, Windsurf), the

+ 2 - 0
docs/architecture/canonical-refactor/04-cli-build-distribution.md

@@ -1,5 +1,7 @@
 # 04 — CLI, Build Pipeline & Distribution
 
+> **Current state (2026-09-02):** This build/distribution analysis is historical. The active metadata workflow is canonical `content/agents/**` `oac:` blocks emitted by RegistryEmitter; sidecar metadata and sidecar merging are superseded.
+
 > **Owner:** Agent D
 > **Status:** Specification (no implementation)
 > **Scope:** The `oac` npm CLI, the neutral-source → per-tool **build pipeline**, full

+ 2 - 0
docs/architecture/canonical-refactor/05-impact-migration-tests.md

@@ -1,5 +1,7 @@
 # 05 — Impact Analysis, Migration Staging & Test Spec
 
+> **Current state (2026-09-02):** This migration plan is historical. The active metadata workflow is canonical `content/agents/**` `oac:` blocks emitted by RegistryEmitter; sidecar metadata and sidecar merging are superseded.
+
 > **Owner:** Agent E
 > **Status:** Spec only — no implementation code. **v2** (2026-07-15): the false "160 agents"
 > figure purged (real count: **34**, disk-verified — see `06-REVIEW` F1); Stage 3 rewritten as

+ 2 - 0
docs/architecture/canonical-refactor/06-REVIEW.md

@@ -1,5 +1,7 @@
 # 06 — Adversarial Cold Review
 
+> **Current state (2026-09-02):** This review records the prior architecture. The active metadata workflow is canonical `content/agents/**` `oac:` blocks emitted by RegistryEmitter; sidecar metadata and sidecar merging are superseded. `eval-runner.md` remains an intentionally registry-carried, non-canonical evaluation harness—not a sidecar replacement.
+
 > **Reviewer:** independent; no involvement in the conversation that produced `00`–`05`.
 > **Method:** every load-bearing claim re-derived from the working tree. Where I could not
 > verify, I say "unverifiable" rather than guess.

+ 2 - 0
docs/maintenance/REGISTRY_FIXES_SUMMARY.md

@@ -1,5 +1,7 @@
 # Installer and Registry Fixes Summary
 
+> **Current state (2026-09-02):** This is a historical fixes summary. Current agent metadata comes only from `content/agents/**` `oac:` blocks through RegistryEmitter; references to the retired metadata sidecar describe the former system.
+
 ## Overview
 This document summarizes the fixes made to resolve installer issues and registry inconsistencies.
 

+ 3 - 1
docs/maintenance/REGISTRY_VALIDATION.md

@@ -1,5 +1,7 @@
 # Registry Validation & Dependency Checking
 
+> **Current state (2026-09-02):** This maintenance record predates the canonical metadata migration. Current agent metadata comes only from `content/agents/**` `oac:` blocks through RegistryEmitter; do not edit or restore the retired metadata sidecar.
+
 This document describes the comprehensive validation system to prevent registry inconsistencies and ensure the installer works correctly.
 
 ## Overview
@@ -193,7 +195,7 @@ npx husky add .husky/pre-commit "bun run scripts/registry/check-dependencies.ts"
 **Solution:**
 1. Check if the dependency was renamed or removed
 2. Update the component's dependencies
-3. Also check `.opencode/config/agent-metadata.json` for agent dependencies
+3. For agents, update dependencies in the canonical `content/agents/**` source and regenerate the registry with `oac build`
 
 ### Issue: Split files not in registry
 

+ 1 - 2
packages/compatibility-layer/src/adapters/OpenCodeAdapter.ts

@@ -45,8 +45,7 @@
  * - **Unknown keys are NOT rejected.** The Agent schema ends in `.catchall(z.any())`
  *   (config.ts:633) and sweeps anything outside `knownKeys` into `options` (config.ts:654-658).
  *   So `oac:` would not actually break OpenCode. Stripping it is hygiene — keeping build-time
- *   metadata out of an agent's runtime `options` — not a hard requirement. See the report in
- *   the subtask notes: the premise that justified `agent-metadata.json` does not hold.
+ *   metadata out of an agent's runtime `options` — not a hard requirement.
  *
  * - **The permission vocabulary is OPEN.** `Permission` is a `z.object({...}).catchall(
  *   PermissionRule).or(PermissionAction)` (config.ts:553-580), and `skill` (config.ts:575) and

+ 13 - 56
packages/compatibility-layer/src/core/AgentLoader.ts

@@ -183,44 +183,6 @@ function extractSections(markdown: string): AgentSections {
   return sections;
 }
 
-// ============================================================================
-// METADATA LOADER (from agent-metadata.json)
-// ============================================================================
-
-interface AgentMetadataFile {
-  agents?: Record<string, Partial<OpenAgent["metadata"]>>;
-}
-
-let cachedMetadata: Record<string, Partial<OpenAgent["metadata"]>> = {};
-let metadataLoaded = false;
-
-/**
- * Load agent metadata from .opencode/config/agent-metadata.json
- * @param projectRoot - Optional project root path
- * @returns Metadata object keyed by agent ID
- */
-function loadMetadataFile(projectRoot?: string): Record<string, Partial<OpenAgent["metadata"]>> {
-  if (metadataLoaded) {
-    return cachedMetadata;
-  }
-
-  const metadataPath = projectRoot
-    ? join(projectRoot, ".opencode/config/agent-metadata.json")
-    : ".opencode/config/agent-metadata.json";
-
-  try {
-    const content = readFileSync(metadataPath, "utf-8");
-    const parsed = JSON.parse(content) as AgentMetadataFile;
-    cachedMetadata = parsed.agents || {};
-  } catch (error) {
-    // Metadata file is optional
-    cachedMetadata = {};
-  }
-
-  metadataLoaded = true;
-  return cachedMetadata;
-}
-
 /**
  * Infer agent ID from file path
  * @param filePath - Path to agent file
@@ -240,11 +202,11 @@ function inferAgentId(filePath: string): string {
  * Loads and parses OpenAgent files from the filesystem
  */
 export class AgentLoader {
-  private projectRoot?: string;
-
-  constructor(projectRoot?: string) {
-    this.projectRoot = projectRoot;
-  }
+  /**
+   * @deprecated The project-root option was used only for retired sidecar enrichment and now
+   * has no effect. Omit it; the constructor remains temporarily compatible with callers.
+   */
+  constructor(_projectRoot?: string) {}
 
   /**
    * Load and parse a single agent file
@@ -281,12 +243,9 @@ export class AgentLoader {
       throw error;
     }
 
-    // Load extended metadata from agent-metadata.json
     const agentId = inferAgentId(filePath);
-    const metadataFile = loadMetadataFile(this.projectRoot);
-    const extendedMetadata = metadataFile[agentId] || {};
 
-    // Merge metadata with defaults
+    // Legacy metadata is derived only from the loaded file and stable defaults.
     const metadata: OpenAgent["metadata"] = {
       id: agentId,
       name: validatedFrontmatter.name,
@@ -294,7 +253,6 @@ export class AgentLoader {
       author: "opencode",
       tags: [],
       dependencies: [],
-      ...extendedMetadata,
     };
 
     // Extract sections from markdown body
@@ -339,7 +297,7 @@ export class AgentLoader {
           await scanDirectory(fullPath);
         } else if (entry.isFile() && entry.name.endsWith(".md")) {
           try {
-            const agent = await new AgentLoader(this.projectRoot).loadFromFile(fullPath);
+            const agent = await new AgentLoader().loadFromFile(fullPath);
             agents.push(agent);
           } catch (error) {
             if (error instanceof AgentLoadError) {
@@ -447,13 +405,12 @@ function listMarkdownFiles(dir: string): string[] {
 /**
  * Loads canonical agent files (OpenCode frontmatter + the `oac:` block) from a content tree.
  *
- * Separate from {@link AgentLoader} on purpose: `AgentLoader` speaks the legacy shape, where
- * identity lives in the `.opencode/config/agent-metadata.json` sidecar and is inferred from
- * the FILENAME. Canonical files carry their own identity in `oac.id`, and the two do not
- * always agree — `content/agents/subagents/code/test-engineer.md` declares `id: tester`, which
- * is what `registry.json`, the profiles and the context docs all reference. Resolving by
- * filename here would silently mint a `test-engineer` that nothing refers to, so this class
- * never infers identity from a path.
+ * Separate from {@link AgentLoader} on purpose: `AgentLoader` preserves the legacy shape and
+ * derives its identity from the filename. Canonical files carry their own identity in `oac.id`,
+ * and the two do not always agree — `content/agents/subagents/code/test-engineer.md` declares
+ * `id: tester`, which is what `registry.json`, the profiles and the context docs all reference.
+ * Resolving by filename here would silently mint a `test-engineer` that nothing refers to, so
+ * this class never infers identity from a path.
  */
 export class CanonicalAgentLoader {
   private readonly contentRoot: string;

+ 2 - 2
packages/compatibility-layer/src/core/ReferenceResolver.ts

@@ -27,8 +27,8 @@
  *
  * The canonical `content/agents/**` tree is NOT part of that corpus — it is not yet wired
  * into any install path. Scan it explicitly with {@link ReferenceResolver.resolveMany} if you
- * want its verdict; be aware it surfaces `subagent:batch-executor`, which exists on disk and
- * in `.opencode/config/agent-metadata.json` but is absent from `registry.json`.
+ * want its verdict; be aware it surfaces `subagent:batch-executor`, which exists on disk but
+ * is absent from `registry.json`.
  */
 
 import { readFileSync, readdirSync, existsSync, statSync } from "node:fs";

+ 130 - 32
packages/compatibility-layer/src/core/RegistryEmitter.ts

@@ -17,36 +17,28 @@
  * This emitter inverts that: the canonical file is the input, the registry entry is OUTPUT.
  * Adding an agent becomes "add one file under `content/agents/`, run the build".
  *
- * ─── Phase 1 scope: agents only ─────────────────────────────────────────────────────────
+ * ─── Phase 2 scope: agents and profiles ─────────────────────────────────────────────────
  *
  * `install.sh` reads `registry.json` and is the only shipping install path, so the blast
- * radius of a mistake here is "nobody can install". Phase 1 therefore regenerates ONLY the
- * `components.agents` and `components.subagents` arrays — the two the canonical tree actually
- * owns — and carries every other part of the document (contexts, commands, tools, plugins,
- * skills, config, profiles, categories, metadata) through verbatim from the committed file.
- * Later subtasks canonicalise the rest.
+ * radius of a mistake here is "nobody can install". The emitter regenerates the
+ * `components.agents` and `components.subagents` arrays and profile component lists from the
+ * canonical trees that own them. It carries all other component categories, profile metadata,
+ * categories, and document metadata through from the committed file. Later subtasks
+ * canonicalise the rest.
  *
  * ─── Which profile list is authoritative ────────────────────────────────────────────────
  *
- * Profiles are authored twice — `.opencode/profiles/<name>/profile.json` and
- * `registry.json` `.profiles.<name>.components` — and they have drifted on all 5 profiles
- * (`advanced`: 51 vs 68), with neither a superset of the other. This emitter treats
- * `registry.json` as authoritative and never consults `profile.json`, because
- * `registry.json` is the list `install.sh` actually reads (`get_profile_components`,
- * install.sh:292) and `profile.json` is read only by `scripts/registry/check-dependencies.ts`,
- * a script no install path invokes. Generating from `profile.json` would silently change what
- * five profiles install. See {@link ProfileLoader.drift} for the disagreement itself; the
- * reconciliation is a content decision, not an emitter one.
+ * Canonical system profiles under `content/profiles/system/` now own install component lists.
+ * Their resolution includes context-profile subscriptions and validates every ref against the
+ * registry. The legacy registry profile retains ownership of presentation metadata such as
+ * `name`, `description`, `badge`, and `additionalPaths`, which canonical profile schemas do
+ * not model yet.
  *
  * ─── Dead refs are repaired in the SOURCE, never here ───────────────────────────────────
  *
- * "Carry non-agent data through verbatim" is the phase-1 contract: this emitter never edits
- * profile lists, even broken ones. The live example was `.profiles.advanced.components`
- * carrying `context:context-system/*`, which expanded to zero matches (the real subtree is
- * `.opencode/context/core/context-system/`). It was repaired 2026-07-17 as a content edit to
- * the committed `registry.json` — the authoritative profile source — not by rewriting it
- * here. Dead refs are reported by {@link ReferenceResolver.resolve} and gated at zero in
- * `tests/unit/build/reference-resolution.test.ts`.
+ * Canonical profile resolution fails closed for dead refs before anything is emitted. Context
+ * wildcards remain literal directory subscriptions (`context:core/*`), rather than expanding
+ * to today's matching files, so profiles also subscribe to future files in that directory.
  *
  * ─── Withdrawing an entry, and why it is manifest-gated ─────────────────────────────────
  *
@@ -78,7 +70,8 @@
  * --exit-code`, so any per-run variation turns that gate into a coin flip (07 Stage 3 /
  * 04 §2.1). Three things guarantee byte-stability here:
  *
- *   - Agent order comes from `oac.id`, not from `readdir`.
+ *   - Agent and profile order comes from canonical ids, not from `readdir`; component refs use
+ *     the same ASCII ordering.
  *   - Key order is fixed: owned entries by literal declaration order, everything else by the
  *     base document's own order, which makes the emitter a fixed point over its own output.
  *   - `metadata.lastUpdated` is carried from the base, never stamped from the clock. The bash
@@ -86,10 +79,11 @@
  *     every rebuild a diff.
  */
 
-import { readFileSync } from "node:fs";
+import { existsSync, readFileSync, readdirSync } from "node:fs";
 import { resolve } from "node:path";
 import { CanonicalAgentLoader, type CanonicalAgentFile } from "./AgentLoader.js";
 import { generatedPaths } from "./BuildManifest.js";
+import { ProfileLoader, type SystemProfileResolution } from "./ProfileLoader.js";
 
 // ============================================================================
 // TYPES
@@ -123,6 +117,11 @@ export interface RegistryDocument {
   [key: string]: unknown;
 }
 
+interface RegistryProfile {
+  components?: string[];
+  [key: string]: unknown;
+}
+
 export interface RegistryEmitterOptions {
   /** Root of the canonical agent tree, relative to the repo root. */
   contentRoot?: string;
@@ -151,6 +150,12 @@ const DEFAULTS = {
   registryFile: "registry.json",
 } as const;
 
+const PROFILES_ROOT = "content/profiles";
+const SYSTEM_PROFILES_ROOT = `${PROFILES_ROOT}/system`;
+
+/** Retired config entries must not survive carry-through from the base registry. */
+const RETIRED_CONFIG_IDS = new Set(["agent-metadata"]);
+
 /** `oac.type` -> the `components` key its entries live under. */
 const CATEGORY_FOR_TYPE: Readonly<Record<string, string>> = {
   agent: "agents",
@@ -162,6 +167,10 @@ function compare(a: string, b: string): number {
   return a < b ? -1 : a > b ? 1 : 0;
 }
 
+function isRecord(value: unknown): value is Record<string, unknown> {
+  return typeof value === "object" && value !== null && !Array.isArray(value);
+}
+
 // ============================================================================
 // SERIALISATION
 // ============================================================================
@@ -211,8 +220,7 @@ export function serializeRegistry(document: RegistryDocument): string {
  *
  * `description` comes from the OpenCode frontmatter rather than the `oac:` block: it is the
  * text the agent itself ships with and the one an installer shows. Everything else comes from
- * `oac:`, which is precisely the content of the `.opencode/config/agent-metadata.json` sidecar
- * this refactor dissolves.
+ * `oac:`, the canonical metadata authored alongside the agent.
  */
 export function entryForAgent(agent: CanonicalAgentFile, agentInstallRoot: string): RegistryEntry {
   return {
@@ -276,10 +284,10 @@ export class RegistryEmitter {
   /**
    * Generate the registry document.
    *
-   * Owned arrays (`agents`, `subagents`) are rebuilt from the canonical tree. Everything else
-   * — including the document's own key order — comes from the base, which is what makes this a
-   * fixed point over its own output: emitting, writing, and emitting again yields the same
-   * bytes.
+   * Owned component arrays and profile component lists are rebuilt from their canonical trees.
+   * Everything else — including the document's own key order — comes from the base, which is
+   * what makes this a fixed point over its own output: emitting, writing, and emitting again
+   * yields the same bytes.
    */
   async emit(): Promise<RegistryDocument> {
     const base = this.base();
@@ -295,7 +303,9 @@ export class RegistryEmitter {
     for (const category of Object.keys(base.components)) {
       const existing = base.components[category] ?? [];
       const owned = generated.get(category);
-      components[category] = owned === undefined ? existing : merge(owned, existing, ours);
+      components[category] = owned === undefined
+        ? this.carried(category, existing)
+        : merge(owned, existing, ours);
     }
 
     // A canonical `oac.type` whose category the base has never seen. Not reachable today
@@ -307,12 +317,22 @@ export class RegistryEmitter {
 
     const document: RegistryDocument = {} as RegistryDocument;
     for (const key of Object.keys(base)) {
-      document[key] = key === "components" ? components : base[key];
+      document[key] = key === "components"
+        ? components
+        : key === "profiles"
+          ? await this.profiles(base, agents)
+          : base[key];
     }
 
     return document;
   }
 
+  /** Preserve non-owned entries except explicit retirement decisions. */
+  private carried(category: string, entries: readonly RegistryEntry[]): RegistryEntry[] {
+    if (category !== "config") return [...entries];
+    return entries.filter((entry) => !RETIRED_CONFIG_IDS.has(entry.id));
+  }
+
   /** The generated registry document, serialised exactly as the committed file is written. */
   async emitJson(): Promise<string> {
     return serializeRegistry(await this.emit());
@@ -335,6 +355,84 @@ export class RegistryEmitter {
 
     return byCategory;
   }
+
+  /** Generate profile component lists when canonical profile content is available. */
+  private async profiles(
+    base: RegistryDocument,
+    agents: readonly CanonicalAgentFile[]
+  ): Promise<unknown> {
+    if (!existsSync(resolve(this.root, PROFILES_ROOT))) return base.profiles;
+
+    const baseProfiles = isRecord(base.profiles) ? base.profiles : {};
+    const loader = new ProfileLoader(this.root);
+    const agentTypes = new Map(agents.map((agent) => [agent.oac.id, agent.oac.type]));
+    const ids = readdirSync(resolve(this.root, SYSTEM_PROFILES_ROOT), { withFileTypes: true })
+      .filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
+      .map((entry) => entry.name.slice(0, -".json".length))
+      .sort(compare);
+
+    const profiles = await Promise.all(
+      ids.map(async (id) => {
+        const resolved = await loader.resolveSystemProfile(id);
+        const baseProfile = baseProfiles[id];
+        return [id, this.profile(baseProfile, resolved, agentTypes, base)] as const;
+      })
+    );
+
+    return Object.fromEntries(profiles);
+  }
+
+  /** Preserve registry-owned profile metadata while replacing the canonical component list. */
+  private profile(
+    base: unknown,
+    resolved: SystemProfileResolution,
+    agentTypes: ReadonlyMap<string, string>,
+    registry: RegistryDocument
+  ): RegistryProfile {
+    const metadata = isRecord(base) ? base : {};
+    return {
+      ...metadata,
+      components: this.references(resolved, agentTypes, registry),
+    };
+  }
+
+  /** Convert a resolved canonical profile closure to install.sh-compatible typed refs. */
+  private references(
+    resolved: SystemProfileResolution,
+    agentTypes: ReadonlyMap<string, string>,
+    registry: RegistryDocument
+  ): string[] {
+    const agentRefs = resolved.agents.map((id) => this.agentReference(id, agentTypes, registry));
+    const refs = [
+      ...agentRefs,
+      ...resolved.contexts.map((id) => `context:${id}`),
+      ...resolved.commands.map((id) => `command:${id}`),
+      ...resolved.tools.map((id) => `tool:${id}`),
+      ...resolved.skills.map((id) => `skill:${id}`),
+      ...resolved.plugins.map((id) => `plugin:${id}`),
+      ...resolved.config.map((id) => `config:${id}`),
+    ];
+
+    return [...new Set(refs)].sort(compare);
+  }
+
+  /** Resolve collapsed canonical agents back to their install.sh registry category. */
+  private agentReference(
+    id: string,
+    agentTypes: ReadonlyMap<string, string>,
+    registry: RegistryDocument
+  ): string {
+    const canonicalType = agentTypes.get(id);
+    if (canonicalType === "agent" || canonicalType === "subagent") return `${canonicalType}:${id}`;
+
+    const category = ["agents", "subagents"].find((key) =>
+      (registry.components[key] ?? []).some((entry) => entry.id === id)
+    );
+    if (category === "agents") return `agent:${id}`;
+    if (category === "subagents") return `subagent:${id}`;
+
+    throw new Error(`Cannot emit profile reference for unknown agent "${id}"`);
+  }
 }
 
 /**

+ 4 - 4
packages/compatibility-layer/src/index.ts

@@ -27,7 +27,7 @@
  * All Zod schemas and TypeScript types for OpenAgents Control.
  * 
  * Includes:
- * - OpenAgent, AgentFrontmatter, AgentMetadata
+ * - OpenAgent, AgentFrontmatter
  * - ToolAccess, PermissionRule, GranularPermission
  * - ContextReference, DependencyReference
  * - ModelIdentifier, TemperatureSchema
@@ -105,11 +105,11 @@ export type { CanonicalAgentFile } from "./core/AgentLoader.js";
 // ============================================================================
 
 /**
- * Generates `registry.json` from the canonical `content/agents/**` tree, replacing the
+ * Generates `registry.json` from canonical component and profile trees, replacing the
  * hand-editing that `scripts/registry/auto-detect-components.sh` only ever appended to.
  *
- * Phase 1 owns `components.agents` and `components.subagents`; contexts, commands, tools,
- * plugins, skills, config and profiles are carried through verbatim from the committed file.
+ * It owns `components.agents`, `components.subagents`, and profile component lists; contexts,
+ * commands, tools, plugins, skills, config, and profile metadata remain carried from the base.
  * Output is byte-stable and a fixed point over itself, so `oac build && git diff --exit-code`
  * is a real drift gate.
  *

+ 8 - 33
packages/compatibility-layer/src/types.ts

@@ -330,8 +330,7 @@ export const AgentFrontmatterSchema = z.object({
 // ============================================================================
 
 /**
- * Stable machine identity. Kebab-case slug — verified against all 28 entries in
- * `.opencode/config/agent-metadata.json`.
+ * Stable machine identity. Kebab-case slug, verified against the canonical agent corpus.
  */
 export const OacIdSchema = z
   .string()
@@ -358,9 +357,8 @@ const CATEGORY_SEGMENT = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
  * Organizational category. Mirrors the agent's directory under `.opencode/agent/`:
  * a closed root vocabulary, optionally followed by one `/`-joined sub-segment.
  *
- * ⚠️ Deliberately a superset of {@link AgentCategorySchema}, which cannot express the
- * corpus: 21 of the 28 entries in `.opencode/config/agent-metadata.json` use values that
- * enum rejects (`subagents/core`, `subagents/code`, …, `testing`). The ratified
+ * ⚠️ Deliberately a superset of {@link AgentCategorySchema}, which cannot express categories
+ * the corpus uses (`subagents/core`, `subagents/code`, …, `testing`). The ratified
  * "the schema must accept its own corpus" rule (02-canonical-schema.md v3) wins over the
  * brief's "reuses AgentCategorySchema". The root stays closed so typos are still caught.
  */
@@ -395,7 +393,7 @@ export const BuildTargetSchema = z.enum([
  * Which platforms this component is emitted to. At least one target is required;
  * `targets: []` is rejected because a component that emits nowhere is dead weight the
  * build would silently skip. Defaults to `["opencode"]` — true of all 34 agents on disk —
- * so an `agent-metadata.json` entry validates as an `oac:` block verbatim.
+ * so a minimal authored `oac:` block needs no boilerplate for the common target.
  */
 export const BuildTargetsSchema = z
   .array(BuildTargetSchema)
@@ -407,8 +405,7 @@ export const BuildTargetsSchema = z
  * - the flat typed string the corpus uses today (`"subagent:tester"`, `"context:standards-code"`)
  * - the structured {@link DependencyReferenceSchema} form
  *
- * Both normalize to `{ type, id }`, so `.opencode/config/agent-metadata.json` round-trips
- * byte-for-byte with zero migration.
+ * Both normalize to `{ type, id }`, giving canonical consumers one stable representation.
  */
 export const DependencyRefInputSchema = z.union([
   z.string().transform((value, ctx): z.infer<typeof DependencyReferenceSchema> => {
@@ -491,9 +488,8 @@ export const TargetOverridesSchema = z
 
 /**
  * The canonical `oac:` frontmatter block — everything a component needs that OpenCode's
- * frontmatter schema rejects as an unknown field. This is precisely the content of
- * `.opencode/config/agent-metadata.json`; carrying it here is what lets that sidecar be
- * dissolved. `oac build` strips this block when emitting OpenCode agent files.
+ * frontmatter schema rejects as an unknown field. `oac build` strips this block when emitting
+ * OpenCode agent files.
  *
  * Strict: an unknown key is an error, never silently dropped.
  */
@@ -611,25 +607,6 @@ export const SystemProfileSchema = z
   })
   .strict();
 
-// ============================================================================
-// Agent Metadata Schema
-// ============================================================================
-
-/**
- * Agent metadata contains identification and organizational information.
- * Stored separately from frontmatter in agent-metadata.json.
- */
-export const AgentMetadataSchema = z.object({
-  id: z.string(),
-  name: z.string(),
-  category: AgentCategorySchema,
-  type: AgentTypeSchema,
-  version: z.string(),
-  author: z.string(),
-  tags: z.array(z.string()).optional().default([]),
-  dependencies: z.array(DependencyReferenceSchema).optional().default([]),
-});
-
 // ============================================================================
 // OpenAgent Schema
 // ============================================================================
@@ -638,8 +615,7 @@ export const AgentMetadataSchema = z.object({
  * Complete OpenAgent schema combining frontmatter, metadata, system prompt,
  * contexts, and optional sections.
  * 
- * This represents the full agent definition after parsing and merging all
- * configuration sources.
+ * This represents the full agent definition after parsing an agent file.
  */
 export const OpenAgentSchema = z.object({
   frontmatter: AgentFrontmatterSchema,
@@ -716,7 +692,6 @@ export type OacBlockInput = z.input<typeof OacBlockSchema>;
 export type CanonicalAgent = z.infer<typeof CanonicalAgentSchema>;
 export type ContextProfile = z.infer<typeof ContextProfileSchema>;
 export type SystemProfile = z.infer<typeof SystemProfileSchema>;
-export type AgentMetadata = z.infer<typeof AgentMetadataSchema>;
 export type OpenAgent = z.infer<typeof OpenAgentSchema>;
 export type ToolConfig = z.infer<typeof ToolConfigSchema>;
 

+ 132 - 0
packages/compatibility-layer/tests/unit/build/registry-profile-emission.test.ts

@@ -0,0 +1,132 @@
+import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { dirname, join } from "node:path";
+import { afterEach, beforeEach, describe, expect, it } from "vitest";
+import { RegistryEmitter } from "../../../src/core/RegistryEmitter.js";
+
+let root: string;
+
+function writeJson(relativePath: string, value: unknown): void {
+  const path = join(root, relativePath);
+  mkdirSync(dirname(path), { recursive: true });
+  writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, "utf-8");
+}
+
+function writeAgent(id: string, type: "agent" | "subagent"): void {
+  const path = join(root, "content/agents", `${id}.md`);
+  mkdirSync(dirname(path), { recursive: true });
+  writeFileSync(
+    path,
+    `---\nname: ${id}\ndescription: ${id} description\nmode: ${type === "agent" ? "primary" : "subagent"}\noac:\n  id: ${id}\n  name: ${id}\n  category: core\n  type: ${type}\n  version: "1.0.0"\n  author: test\n  tags: []\n  dependencies: []\n  targets:\n    - opencode\n---\n`,
+    "utf-8"
+  );
+}
+
+function fixture(withProfiles = true): void {
+  mkdirSync(join(root, "content/agents"), { recursive: true });
+  writeAgent("primary", "agent");
+  writeAgent("worker", "subagent");
+  writeJson("registry.json", {
+    components: {
+      agents: [{ id: "primary", path: ".opencode/agent/primary.md" }, { id: "eval-runner", path: ".opencode/agent/eval-runner.md" }],
+      subagents: [{ id: "worker", path: ".opencode/agent/worker.md" }],
+      contexts: [{ id: "core-context", path: ".opencode/context/core/example.md" }],
+      commands: [{ id: "build", path: ".opencode/command/build.md" }],
+      tools: [{ id: "shell", path: ".opencode/tool/shell.md" }],
+      skills: [{ id: "testing", path: ".opencode/skills/testing/SKILL.md" }],
+      plugins: [{ id: "hooks", path: ".opencode/plugin/hooks.ts" }],
+      config: [{ id: "defaults", path: ".opencode/config/defaults.json" }],
+    },
+    profiles: {
+      selected: {
+        name: "Selected",
+        description: "Base metadata remains registry-owned.",
+        badge: "stable",
+        additionalPaths: [".env"],
+        components: ["agent:stale"],
+      },
+      legacy: {
+        name: "Legacy",
+        description: "Has no canonical source.",
+        components: ["agent:primary"],
+      },
+    },
+  });
+
+  if (!withProfiles) return;
+
+  writeJson("content/profiles/context/core.json", { id: "core", contexts: ["core/*"] });
+  writeJson("content/profiles/system/selected.json", {
+    id: "selected",
+    agents: ["worker", "primary", "eval-runner"],
+    contextProfiles: ["core"],
+    commands: ["build"],
+    tools: ["shell"],
+    skills: ["testing"],
+    plugins: ["hooks"],
+    config: ["defaults"],
+  });
+}
+
+describe("RegistryEmitter profile emission", () => {
+  beforeEach(() => {
+    root = mkdtempSync(join(tmpdir(), "oac-registry-profiles-"));
+  });
+
+  afterEach(() => {
+    rmSync(root, { recursive: true, force: true });
+  });
+
+  it("generates install-compatible canonical components while preserving base metadata", async () => {
+    fixture();
+
+    const document = await new RegistryEmitter(root).emit();
+
+    expect(document.profiles).toEqual({
+      selected: {
+        name: "Selected",
+        description: "Base metadata remains registry-owned.",
+        badge: "stable",
+        additionalPaths: [".env"],
+        components: [
+          "agent:eval-runner",
+          "agent:primary",
+          "command:build",
+          "config:defaults",
+          "context:core/*",
+          "plugin:hooks",
+          "skill:testing",
+          "subagent:worker",
+          "tool:shell",
+        ],
+      },
+    });
+  });
+
+  it("drops base profiles that have no canonical system profile when canonical profiles exist", async () => {
+    fixture();
+
+    const document = await new RegistryEmitter(root).emit();
+
+    expect(document.profiles).not.toHaveProperty("legacy");
+  });
+
+  it("preserves base profiles when canonical profile content is absent", async () => {
+    fixture(false);
+
+    const document = await new RegistryEmitter(root).emit();
+
+    expect(document.profiles).toEqual(JSON.parse(readFileSync(join(root, "registry.json"), "utf-8")).profiles);
+  });
+
+  it("is byte-stable after emit, write, and emit", async () => {
+    fixture();
+    const emitter = new RegistryEmitter(root);
+    const first = await emitter.emitJson();
+    writeFileSync(join(root, "registry.json"), first, "utf-8");
+
+    const second = await new RegistryEmitter(root).emitJson();
+
+    expect(second).toBe(first);
+  });
+});

+ 3 - 4
packages/compatibility-layer/tests/unit/build/system-profile-equivalence.test.ts

@@ -5,10 +5,9 @@
  * ─── Why this test exists ───────────────────────────────────────────────────────────────
  *
  * Seeding `content/profiles/**` was a translation of the legacy hand-maintained lists, and
- * any translation can silently drop or invent a component. Until the registry emitter
- * generates `.profiles` from the canonical source (subtask 08's pending emission), BOTH
- * lists are live: `install.sh` reads the registry, the canonical build reads `content/`.
- * If they ever disagree, the two install paths ship two different products.
+ * any translation can silently drop or invent a component. The registry emitter generates
+ * `.profiles` from canonical content, while `install.sh` reads the generated registry. If they
+ * ever disagree, the build would ship two different products.
  *
  * This test is the drift alarm for that window. It fails the moment someone edits one side
  * without the other — which is precisely the failure mode that put 51-vs-68-component drift

+ 91 - 41
packages/compatibility-layer/tests/unit/core/RegistryEmitter.test.ts

@@ -60,8 +60,8 @@ const NONDETERMINISM = [
  * Verified on disk 2026-07-15. `batch-executor` is the load-bearing one: both `openagent` and
  * `opencoder` declare `subagent:batch-executor`, so the registry advertised a dependency on a
  * component it did not contain. The other six were in neither the registry nor
- * `.opencode/config/agent-metadata.json` — they were added to `.opencode/agent/` and nothing
- * ever noticed. `auto-detect-components.sh --dry-run` reported all 7 as "New Components".
+  * the canonical corpus — they were added to `.opencode/agent/` and nothing ever noticed.
+  * `auto-detect-components.sh --dry-run` reported all 7 as "New Components".
  *
  * ─── Updated by subtask 10, deliberately ────────────────────────────────────────────────
  *
@@ -163,6 +163,25 @@ describe("serializeRegistry", () => {
 // ============================================================================
 
 describe("determinism", () => {
+  it("emits canonical agent entries without an agent-metadata sidecar", async () => {
+    // The scratch root has only registry.json: canonical agent metadata is loaded from content.
+    const root = scratchRoot(COMMITTED_JSON);
+
+    try {
+      const document = await new RegistryEmitter(root, {
+        contentRoot: repoPath("content/agents"),
+      }).emit();
+
+      expect(byId(document, "agents").get("openagent")).toMatchObject({
+        id: "openagent",
+        name: "OpenAgent",
+        type: "agent",
+      });
+    } finally {
+      rmSync(root, { recursive: true, force: true });
+    }
+  });
+
   it("emits byte-identical output across two runs", async () => {
     expect(await emitRegistry(repoPath())).toBe(await emitRegistry(repoPath()));
   });
@@ -212,14 +231,16 @@ describe("determinism", () => {
 });
 
 // ============================================================================
-// CARRY-THROUGH — phase 1 owns agents only
+// CARRY-THROUGH — canonical trees own agents and profile components
 // ============================================================================
 
 describe("carry-through of everything the canonical tree does not own", () => {
-  it("leaves non-agent component categories byte-identical", async () => {
+  it("leaves non-agent component categories byte-identical except retired config", async () => {
     const document = await new RegistryEmitter(repoPath()).emit();
     const carried = Object.keys(COMMITTED.components).filter(
-      (category) => !GENERATED_CATEGORIES.includes(category as (typeof GENERATED_CATEGORIES)[number])
+      (category) =>
+        category !== "config" &&
+        !GENERATED_CATEGORIES.includes(category as (typeof GENERATED_CATEGORIES)[number])
     );
 
     expect(carried).toContain("contexts");
@@ -230,11 +251,20 @@ describe("carry-through of everything the canonical tree does not own", () => {
     }
   });
 
-  it("leaves every non-components top-level key untouched, in order", async () => {
+  it("does not carry the retired agent-metadata config entry", async () => {
+    const document = await new RegistryEmitter(repoPath()).emit();
+
+    expect(byId(document, "config").has("agent-metadata")).toBe(false);
+    expect(entries(document, "config")).toEqual(
+      entries(COMMITTED, "config").filter((entry) => entry.id !== "agent-metadata")
+    );
+  });
+
+  it("leaves every non-components, non-profiles top-level key untouched, in order", async () => {
     const document = await new RegistryEmitter(repoPath()).emit();
 
     expect(Object.keys(document)).toEqual(Object.keys(COMMITTED));
-    for (const key of Object.keys(COMMITTED).filter((k) => k !== "components")) {
+    for (const key of Object.keys(COMMITTED).filter((k) => k !== "components" && k !== "profiles")) {
       expect(document[key], `top-level "${key}" was modified`).toEqual(COMMITTED[key]);
     }
   });
@@ -259,21 +289,31 @@ describe("carry-through of everything the canonical tree does not own", () => {
     ]);
   });
 
-  it("keeps registry profiles verbatim and never consults profile.json", async () => {
-    // `.profiles` is what install.sh reads (get_profile_components, install.sh:292).
-    // `.opencode/profiles/<name>/profile.json` has drifted from it on all 5 profiles and is
-    // read only by check-dependencies.ts, which no install path invokes. Reconciling them is
-    // a content decision; generating from the wrong one silently changes what users install.
+  it("preserves registry-owned profile metadata while regenerating components", async () => {
     const document = await new RegistryEmitter(repoPath()).emit();
+    const profiles = document.profiles as Record<string, RegistryEntry>;
+    const committedProfiles = COMMITTED.profiles as Record<string, RegistryEntry>;
+
+    for (const id of Object.keys(committedProfiles)) {
+      const { components: expectedComponents, ...expectedMetadata } = committedProfiles[id];
+      const { components: actualComponents, ...actualMetadata } = profiles[id];
+      expect(actualMetadata, `${id} metadata changed`).toEqual(expectedMetadata);
+      expect(actualComponents).toBeDefined();
+    }
+  });
+
+  it("does not emit retired config references for canonical profiles", async () => {
+    const document = await new RegistryEmitter(repoPath()).emit();
+    const profiles = document.profiles as Record<string, { components: string[] }>;
 
-    expect(document.profiles).toEqual(COMMITTED.profiles);
+    for (const id of ["essential", "developer", "business", "advanced", "full"]) {
+      expect(profiles[id]?.components).not.toContain("config:agent-metadata");
+    }
   });
 
   it("leaves the advanced-profile wildcard exactly as authored", async () => {
-    // This ref spent 2026-07-15..17 authored as the dead `context:context-system/*`, and the
-    // emitter carried THAT verbatim too — the repair to `core/context-system/*` was a content
-    // edit to the committed registry.json, not an emitter rewrite. Profile lists pass through
-    // untouched either way; that is the contract this test pins.
+    // Wildcards are directory subscriptions, so canonical profile emission preserves them
+    // literally rather than expanding today's matching contexts.
     const document = await new RegistryEmitter(repoPath()).emit();
     const advanced = document.profiles as Record<string, { components: string[] }>;
 
@@ -281,23 +321,32 @@ describe("carry-through of everything the canonical tree does not own", () => {
     expect(advanced.advanced?.components).not.toContain("context:context-system/*");
   });
 
-  it("carries agents that have no canonical counterpart", async () => {
+  it("carries eval-runner even though it is deliberately absent from canonical content", async () => {
     // `agent:eval-runner` ships in `.opencode/agent/eval-runner.md` and is in the committed
-    // registry, but `content/agents/` does not carry it yet. Generating agents purely from the
-    // canonical tree would delete it and uninstall the eval harness.
+    // registry, but retirement deliberately keeps it out of `content/agents/`. Generating
+    // agents purely from the canonical tree would delete it and uninstall the eval harness.
+
+    // Arrange
     const document = await new RegistryEmitter(repoPath()).emit();
+    const canonical = await new CanonicalAgentLoader(repoPath("content/agents")).loadFromDirectory();
 
-    expect(byId(document, "agents").get("eval-runner")).toEqual(
-      byId(COMMITTED, "agents").get("eval-runner")
-    );
+    // Act
+    const canonicalIds = new Set(canonical.map((agent) => agent.oac.id));
+    const emitted = byId(document, "agents").get("eval-runner");
+
+    // Assert
+    expect(canonicalIds.has("eval-runner")).toBe(false);
+
+    expect(emitted).toEqual(byId(COMMITTED, "agents").get("eval-runner"));
   });
 
-  it("never drops an id the committed registry publishes", async () => {
+  it("never drops a non-retired id the committed registry publishes", async () => {
     const document = await new RegistryEmitter(repoPath()).emit();
 
     for (const category of Object.keys(COMMITTED.components)) {
       const generated = byId(document, category);
       const dropped = entries(COMMITTED, category)
+        .filter((entry) => category !== "config" || entry.id !== "agent-metadata")
         .map((entry) => entry.id)
         .filter((id) => !generated.has(id));
 
@@ -434,25 +483,26 @@ describe("withdrawal of entries the build itself generated", () => {
 // ============================================================================
 
 describe("generation from the canonical tree", () => {
-  it("derives every field of an entry from the canonical file", async () => {
+  it("derives each canonical entry's identity, tags, category, version, and dependencies from content", async () => {
+    // Arrange
     const document = await new RegistryEmitter(repoPath()).emit();
     const agents = await new CanonicalAgentLoader(repoPath("content/agents")).loadFromDirectory();
-    const tester = agents.find((agent) => agent.oac.id === "tester");
-
-    expect(tester).toBeDefined();
-    expect(byId(document, "subagents").get("tester")).toEqual({
-      id: "tester",
-      name: "TestEngineer",
-      type: "subagent",
-      // Identity is oac.id, never the filename: the file is `test-engineer.md` but the
-      // registry, the profiles and every context doc reference `tester`.
-      path: ".opencode/agent/subagents/code/test-engineer.md",
-      version: "1.0.0",
-      description: tester!.frontmatter.description,
-      tags: ["testing", "tdd", "quality"],
-      dependencies: ["context:standards-tests"],
-      category: "subagents/code",
-    });
+    const emitted = new Map(
+      [...entries(document, "agents"), ...entries(document, "subagents")].map((entry) => [
+        entry.id,
+        entry,
+      ])
+    );
+
+    // Act
+    const mismatches = agents
+      .map((agent) => ({ id: agent.oac.id, expected: entryForAgent(agent, ".opencode/agent") }))
+      .filter(({ id, expected }) => JSON.stringify(emitted.get(id)) !== JSON.stringify(expected));
+
+    // Assert
+    expect(mismatches, "canonical registry entries must be wholly derived from their content file").toEqual(
+      []
+    );
   });
 
   it("emits entry keys in the committed field order", async () => {

+ 13 - 54
packages/compatibility-layer/tests/unit/schema/canonical-agent.test.ts

@@ -141,7 +141,7 @@ describe("canonical agent files", () => {
 });
 
 // ============================================================================
-// RED — the real content/ corpus (subtask 09)
+// CANONICAL CORPUS — real authored files and the registry-carried exception
 // ============================================================================
 
 describe("content/agents corpus", () => {
@@ -166,32 +166,23 @@ describe("content/agents corpus", () => {
     expect(rejected.join("\n") || "", "files rejected by CanonicalAgentSchema").toBe("");
   });
 
-  // SKIPPED — one known gap, deliberate: eval-runner.md has uncommitted user work in the
-  // working tree, so subtask 09 did not seed it. It is a real remaining gap, not a permanent
-  // exclusion. Un-skip once that work is committed and eval-runner is seeded into content/.
-  it.skip("covers every agent under .opencode/agent/ [BLOCKED: eval-runner has uncommitted work]", () => {
+  it("deliberately excludes registry-carried eval-runner from the canonical corpus", () => {
+    // Arrange
     const dir = requireDir(
       "content/agents",
       OWED_BY,
-      "every agent under .opencode/agent/ has been seeded into content/agents/ — task.json's " +
-        "exit criterion is that all 34 load from content/"
+      "the canonical corpus must be available to verify its registry-carried exception"
     );
+    const canonical = new Set(listFiles(dir).map((file) => basename(file, ".md")));
 
-    // Compared against .opencode/agent/ directly rather than a literal 34, so this tracks the
-    // tree instead of a number that goes stale.
-    const seeded = new Set(listFiles(dir).map((file) => basename(file)));
-    const missing = listFiles(requireDir(".opencode/agent", "n/a — already on disk", "n/a"))
-      .map((file) => basename(file))
-      .filter((file) => !seeded.has(file));
+    // Act
+    const hasEvalRunner = canonical.has("eval-runner");
 
+    // Assert
     expect(
-      missing,
-      "agents not yet seeded into content/agents/.\n" +
-        "  Known gap as of 2026-07-15: eval-runner.md is skipped because it has uncommitted\n" +
-        "  user work in the working tree. It IS in agent-metadata.json and task.json's exit\n" +
-        "  criteria require all 34 in content/, so this is a real remaining gap, not a\n" +
-        "  permanent exclusion — subtask 09 must seed it once that work is committed."
-    ).toEqual([]);
+      hasEvalRunner,
+      "eval-runner is deliberately registry-carried and must not become a canonical source requirement"
+    ).toBe(false);
   });
 
   it("gives every agent a unique oac id", () => {
@@ -201,9 +192,8 @@ describe("content/agents corpus", () => {
       "agent ids are unique, so the build can address each agent unambiguously"
     );
 
-    // NB: the id deliberately does NOT have to match the filename. `test-engineer.md` has
-    // id `tester` in .opencode/config/agent-metadata.json, and `subagent:tester` is what the
-    // profiles and registry reference. The id is the identity; the path is just where it sits.
+    // NB: the id deliberately does NOT have to match the filename. `test-engineer.md` declares
+    // id `tester`; the id is the identity and the path is just where it sits.
     const ids = listFiles(dir).flatMap((file) => {
       const { data } = matter(readFileSync(file, "utf-8"));
       const parsed = OacBlockSchema.safeParse(data.oac);
@@ -218,37 +208,6 @@ describe("content/agents corpus", () => {
     expect(ids.length, "no agent file parsed — is content/agents/ populated?").toBeGreaterThan(0);
   });
 
-  // SKIPPED — same single gap as above: eval-runner is the only sidecar id content/ does not
-  // carry, because its file has uncommitted user work. Un-skip with the test above.
-  it.skip("keeps every id that agent-metadata.json already knows [BLOCKED: eval-runner has uncommitted work]", () => {
-    const dir = requireDir(
-      "content/agents",
-      OWED_BY,
-      "seeding content/agents/ preserves the ids the sidecar already published, so registry " +
-        "and profile references keep resolving once the sidecar is dissolved"
-    );
-
-    const sidecar = JSON.parse(
-      readFileSync(packagePath("../../.opencode/config/agent-metadata.json"), "utf-8")
-    ) as { agents: Record<string, { id: string }> };
-
-    const seeded = new Set(
-      listFiles(dir).flatMap((file) => {
-        const { data } = matter(readFileSync(file, "utf-8"));
-        const parsed = OacBlockSchema.safeParse(data.oac);
-        return parsed.success ? [parsed.data.id] : [];
-      })
-    );
-
-    const lost = Object.values(sidecar.agents)
-      .map((entry) => entry.id)
-      .filter((id) => !seeded.has(id));
-
-    expect(lost, "ids published by agent-metadata.json that content/agents/ no longer carries").toEqual(
-      []
-    );
-  });
-
   it("emits no oac: key into any generated OpenCode agent file", () => {
     // The inverse of the corpus check: `oac:` is authoring-only. If it survives into
     // .opencode/agent/**, OpenCode rejects the file as an unknown field.

+ 88 - 54
packages/compatibility-layer/tests/unit/types/OacBlock.test.ts

@@ -1,47 +1,24 @@
 /**
  * Unit tests for the canonical `oac:` frontmatter block.
  *
- * The block carries what `.opencode/config/agent-metadata.json` holds today. The sidecar
- * exists only because OpenCode rejects unknown frontmatter keys; `oac build` strips the
- * block on emit, which is what lets the sidecar be dissolved.
+ * The block is the canonical metadata carried by every authored agent. OpenCode rejects
+ * unknown frontmatter keys, so `oac build` strips this authoring-only block on emit.
  *
  * The load-bearing test here is `accepts every entry in the real corpus` — a schema that
  * does not accept its own corpus is not a schema, it is a wish.
  */
 
 import { describe, it, expect } from "vitest";
-import { readFileSync } from "node:fs";
+import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
 import { dirname, join } from "node:path";
-import { fileURLToPath } from "node:url";
 import {
-  OacBlockFieldsSchema,
   OacBlockSchema,
   CanonicalAgentSchema,
   OacCategorySchema,
   BuildTargetsSchema,
 } from "../../../src/types.js";
-
-const HERE = dirname(fileURLToPath(import.meta.url));
-const REPO_ROOT = join(HERE, "../../../../..");
-const METADATA_PATH = join(REPO_ROOT, ".opencode/config/agent-metadata.json");
-
-interface MetadataEntry {
-  id: string;
-  name: string;
-  category: string;
-  type: string;
-  version: string;
-  author: string;
-  tags?: string[];
-  dependencies?: string[];
-}
-
-function corpus(): Record<string, MetadataEntry> {
-  const raw = JSON.parse(readFileSync(METADATA_PATH, "utf-8")) as {
-    agents: Record<string, MetadataEntry>;
-  };
-  return raw.agents;
-}
+import { AgentLoader, CanonicalAgentLoader } from "../../../src/core/AgentLoader.js";
 
 const VALID_BLOCK = {
   id: "openagent",
@@ -55,6 +32,54 @@ const VALID_BLOCK = {
   targets: ["opencode", "claude-code"],
 };
 
+/**
+ * A customer-style tree whose retired sidecar deliberately contradicts the authored agent.
+ * The fixture, rather than a filesystem spy, makes sidecar use observable in loaded output.
+ */
+function createConflictingSidecarFixture(): {
+  root: string;
+  agentPath: string;
+  contentRoot: string;
+} {
+  const root = mkdtempSync(join(tmpdir(), "oac-retired-sidecar-"));
+  const agentPath = join(root, "content/agents/fixture-agent.md");
+  const contentRoot = join(root, "content/agents");
+
+  mkdirSync(dirname(agentPath), { recursive: true });
+  writeFileSync(
+    agentPath,
+    `---
+name: Canonical Fixture Agent
+description: Canonical fixture metadata wins
+mode: subagent
+oac:
+  id: canonical-fixture-agent
+  name: Canonical Fixture Agent
+  category: core
+  type: agent
+---
+Fixture body.\n`,
+    "utf-8"
+  );
+  mkdirSync(join(root, ".opencode/config"), { recursive: true });
+  writeFileSync(
+    join(root, ".opencode/config/agent-metadata.json"),
+    `${JSON.stringify({
+      agents: {
+        "fixture-agent": {
+          id: "sidecar-fixture-agent",
+          name: "Sidecar Override",
+          category: "meta",
+          type: "subagent",
+        },
+      },
+    })}\n`,
+    "utf-8"
+  );
+
+  return { root, agentPath, contentRoot };
+}
+
 describe("OacBlockSchema", () => {
   describe("valid blocks", () => {
     it("accepts a fully populated block", () => {
@@ -241,34 +266,43 @@ describe("OacBlockSchema", () => {
     });
   });
 
-  describe("the real corpus", () => {
-    it("accepts every entry in .opencode/config/agent-metadata.json", () => {
-      const entries = Object.entries(corpus());
-      expect(entries.length).toBeGreaterThan(0);
-
-      const rejected = entries
-        .map(([key, entry]) => ({ key, result: OacBlockSchema.safeParse(entry) }))
-        .filter(({ result }) => !result.success)
-        .map(({ key, result }) => `${key}: ${JSON.stringify(result.error?.issues)}`);
-
-      expect(rejected).toEqual([]);
-    });
-
-    it("covers every field the sidecar uses, so nothing is lost dissolving it", () => {
-      const used = new Set(Object.values(corpus()).flatMap((entry) => Object.keys(entry)));
-      // OacBlockSchema carries cross-field refinements, which makes it a ZodEffects with no
-      // `.shape`. The field list lives on the object it wraps.
-      const known = new Set(Object.keys(OacBlockFieldsSchema.shape));
-
-      expect([...used].filter((field) => !known.has(field))).toEqual([]);
+  describe("retired sidecar isolation", () => {
+    it("loads canonical identity from the authored oac block when a conflicting sidecar exists", async () => {
+      // Arrange
+      const fixture = createConflictingSidecarFixture();
+      const loader = new CanonicalAgentLoader(fixture.contentRoot);
+
+      try {
+        // Act
+        const agents = await loader.loadFromDirectory();
+
+        // Assert
+        expect(agents).toHaveLength(1);
+        expect(agents[0]?.oac).toMatchObject({
+          id: "canonical-fixture-agent",
+          name: "Canonical Fixture Agent",
+        });
+      } finally {
+        rmSync(fixture.root, { recursive: true, force: true });
+      }
     });
 
-    it("round-trips sidecar dependency strings back to their authored form", () => {
-      for (const entry of Object.values(corpus())) {
-        const parsed = OacBlockSchema.parse(entry);
-        const reemitted = parsed.dependencies.map((dep) => `${dep.type}:${dep.id}`);
-
-        expect(reemitted).toEqual(entry.dependencies ?? []);
+    it("does not merge retired sidecar metadata into the legacy runtime loader", async () => {
+      // Arrange
+      const fixture = createConflictingSidecarFixture();
+      const loader = new AgentLoader(fixture.root);
+
+      try {
+        // Act
+        const agent = await loader.loadFromFile(fixture.agentPath);
+
+        // Assert — intentionally RED until subtask 03 retires the runtime sidecar merge.
+        expect(agent.metadata).toMatchObject({
+          id: "fixture-agent",
+          name: "Canonical Fixture Agent",
+        });
+      } finally {
+        rmSync(fixture.root, { recursive: true, force: true });
       }
     });
   });

+ 0 - 10
plugins/claude-code/agents/code-reviewer.md

@@ -73,12 +73,6 @@ task(subagent_type="ContextScout", description="Find code review standards", pro
 3. Flag deviations from team standards as findings
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
----
-
 ## What NOT to Do
 
 - ❌ **Don't skip ContextScout** — reviewing without project standards = generic feedback that misses project-specific issues
@@ -89,10 +83,6 @@ task(subagent_type="ContextScout", description="Find code review standards", pro
 - ❌ **Don't skip error handling checks** — missing error handling is a correctness issue
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
   <context_first>ContextScout before any review — standards-blind reviews are useless</context_first>
   <security_first>Security findings always surface first — they have the highest impact</security_first>
   <read_only>Suggest, never apply — the developer owns the fix</read_only>

+ 0 - 12
plugins/claude-code/agents/coder-agent.md

@@ -78,12 +78,6 @@ task(subagent_type="ContextScout", description="Find coding standards for [featu
 3. If ContextScout flags a framework/library → call **ExternalScout** for live docs (see below)
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
----
-
 ## Workflow
 
 ### Step 1: Read Subtask JSON
@@ -231,12 +225,6 @@ Summary: Implemented JWT authentication with refresh tokens and error handling
 - Status marking is the signal that enables parallel workflow progression
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
----
-
 ## Principles
 
 - Context first, code second. Always.

+ 0 - 20
plugins/claude-code/agents/context-manager.md

@@ -69,10 +69,6 @@ model: sonnet
 <task>Discover context structure via navigation → catalog existing context → validate integrity → propose improvements → maintain lifecycle</task>
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 ## 📋 Process Flow
@@ -203,10 +199,6 @@ model: sonnet
 </process_flow>
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
   <parameter name="request_type" type="enum">
     Type of context management request:
     - "discover": Discover and map context structure
@@ -333,10 +325,6 @@ model: sonnet
 </output_specification>
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
   <pre_execution>
     - Verify request_type is valid
     - Verify scope exists or is "all"
@@ -395,10 +383,6 @@ model: sonnet
 </context_management_principles>
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 ## 📝 Common Operations
@@ -439,10 +423,6 @@ Details: Overall context health and maintenance recommendations
 ```
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 **ContextManager** - Organize, validate, and maintain your project context!

+ 0 - 19
plugins/claude-code/agents/external-scout.md

@@ -60,10 +60,6 @@ model: haiku
 </critical_rules>
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
   <tier level="1" desc="Critical Operations">
     - @check_cache_first: Check .tmp/external-context/ before fetching
     - @tool_usage: Use ONLY allowed tools
@@ -222,10 +218,6 @@ model: haiku
 </workflow_execution>
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 ## Quick Reference
@@ -235,10 +227,6 @@ model: haiku
 **Supported Libraries**: Drizzle | Prisma | Better Auth | NextAuth.js | Clerk | Next.js | React | TanStack Query/Router | Cloudflare Workers | AWS Lambda | Vercel | Shadcn/ui | Radix UI | Tailwind CSS | Zustand | Jotai | Zod | React Hook Form | Vitest | Playwright
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
     ├── cloudflare-deployment.md
     ├── server-functions.md
     └── file-routing.md
@@ -277,10 +265,6 @@ If Context7 API fails:
 3. Suggest checking `.opencode/context/` for cached docs
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 
 ## Success Criteria
@@ -300,6 +284,3 @@ You succeed when ALL of these are complete:
 - Return summary without file locations
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json

+ 0 - 8
plugins/claude-code/agents/test-engineer.md

@@ -73,10 +73,6 @@ task(subagent_type="ContextScout", description="Find testing standards", prompt=
 3. Structure your test plan to match project conventions
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
    - ✅ Positive: [expected success outcome]
    - ❌ Negative: [expected failure/edge case handling]
    - ✅ Positive: [expected success outcome]
@@ -94,10 +90,6 @@ task(subagent_type="ContextScout", description="Find testing standards", prompt=
 - ❌ **Don't skip the test plan** — propose before implementing, get approval
 
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
   <context_first>ContextScout before any test writing — conventions matter</context_first>
   <tdd_mindset>Think about testability before implementation — tests define behavior</tdd_mindset>
   <deterministic>Tests must be reliable — no flakiness, no external dependencies</deterministic>

+ 3 - 3
scripts/README.md

@@ -42,7 +42,7 @@ See `tests/` subdirectory for installer test scripts.
 
 ### Registry Management
 
-- `registry/auto-detect-components.sh` - Auto-detect new components in .opencode/
+- `registry/auto-detect-components.sh` - Legacy scanner for non-canonical `.opencode/` components; canonical agents are emitted from `content/agents/**` by `oac build`
 - `registry/register-component.sh` - Register a new component in the registry
 - `registry/validate-component.sh` - Validate component structure and metadata
 - `registry/validate-registry.sh` - Validate all registry paths
@@ -100,10 +100,10 @@ Sessions are safe to delete anytime - they only contain temporary context files
 ### Registry Management
 
 ```bash
-# Auto-detect new components
+# Inspect non-canonical .opencode components (does not generate canonical agents)
 ./scripts/registry/auto-detect-components.sh --dry-run
 
-# Add new components to registry
+# Update residual non-canonical components only
 ./scripts/registry/auto-detect-components.sh --auto-add
 
 # Validate registry

+ 6 - 39
scripts/registry/auto-detect-components.sh

@@ -2,9 +2,11 @@
 
 #############################################################################
 # Auto-Detect Components Script v2.0.0
-# Scans .opencode directory for new components not in registry
-# Validates existing entries, fixes typos, removes deleted components
-# Performs security checks on component files
+# Legacy maintenance utility for non-canonical `.opencode` components.
+# Canonical agent registry entries are emitted from `content/agents/**` by
+# RegistryEmitter during `oac build`; this script does not read sidecar metadata.
+# Validates existing entries, fixes typos, removes deleted components, and
+# performs security checks on component files.
 #############################################################################
 
 set -e
@@ -366,7 +368,7 @@ extract_metadata_from_file() {
     local tags=""
     local dependencies=""
     
-    # Generate ID from filename first (needed for metadata lookup)
+    # Generate the component ID from its filename.
     local filename
     filename=$(basename "$file" .md)
     id=$(echo "$filename" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')
@@ -455,41 +457,6 @@ extract_metadata_from_file() {
     # Generate name from filename (capitalize words)
     name=$(echo "$filename" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2))}1')
     
-    # Check if agent-metadata.json exists and merge metadata from it
-    local metadata_file="$REPO_ROOT/.opencode/config/agent-metadata.json"
-    if [ -f "$metadata_file" ] && command -v jq &> /dev/null; then
-        # Try to find metadata for this agent ID
-        local metadata_entry
-        metadata_entry=$(jq -r ".agents[\"$id\"] // empty" "$metadata_file" 2>/dev/null)
-        
-        if [ -n "$metadata_entry" ] && [ "$metadata_entry" != "null" ]; then
-            # Override name if present in metadata
-            local meta_name
-            meta_name=$(echo "$metadata_entry" | jq -r '.name // empty' 2>/dev/null)
-            if [ -n "$meta_name" ] && [ "$meta_name" != "null" ]; then
-                name="$meta_name"
-            fi
-            
-            # Merge tags (prefer frontmatter, fallback to metadata)
-            if [ -z "$tags" ]; then
-                local meta_tags
-                meta_tags=$(echo "$metadata_entry" | jq -r '.tags // [] | join(",")' 2>/dev/null)
-                if [ -n "$meta_tags" ] && [ "$meta_tags" != "null" ]; then
-                    tags="$meta_tags"
-                fi
-            fi
-            
-            # Merge dependencies (prefer frontmatter, fallback to metadata)
-            if [ -z "$dependencies" ]; then
-                local meta_deps
-                meta_deps=$(echo "$metadata_entry" | jq -r '.dependencies // [] | join(",")' 2>/dev/null)
-                if [ -n "$meta_deps" ] && [ "$meta_deps" != "null" ]; then
-                    dependencies="$meta_deps"
-                fi
-            fi
-        fi
-    fi
-    
     echo "${id}|${name}|${description}|${tags}|${dependencies}"
 }
 

+ 74 - 69
scripts/tests/test-e2e-install.sh

@@ -11,7 +11,7 @@ NC='\033[0m'
 
 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
 REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
-TEST_DIR="/tmp/opencode-e2e-test-$$"
+TEST_DIR="$(mktemp -d "${TMPDIR:-/tmp}/opencode-e2e-test.XXXXXX")"
 PASSED=0
 FAILED=0
 
@@ -49,6 +49,69 @@ print_header() {
     echo -e "${NC}"
 }
 
+# Assert the registry-declared closure rather than a retired generated sidecar.
+profile_component_paths() {
+    local profile=$1
+
+    jq -r --arg profile "$profile" '
+        . as $registry
+        | .profiles[$profile].components[]
+        | split(":") as [$type, $id]
+        | if ($id | endswith("*")) then
+            if $type == "context" then
+                ($id | sub("\\*$"; "")) as $prefix
+                | $registry.components.contexts[]
+                | select(.path | startswith(".opencode/context/" + $prefix))
+                | (.files // [.path])[]
+            else
+                empty
+            end
+        else
+            (if $type == "agent" then "agents"
+             elif $type == "subagent" then "subagents"
+             elif $type == "command" then "commands"
+             elif $type == "context" then "contexts"
+             elif $type == "config" then "config"
+             else $type + "s"
+             end) as $collection
+            | first($registry.components[$collection][]? | select(.id == $id)) as $component
+            | if $component == null then empty else ($component.files // [$component.path])[] end
+        end
+        | sub("^\\.opencode/"; "")
+    ' "$REPO_ROOT/registry.json"
+}
+
+test_profile_closure() {
+    local profile=$1
+    local install_dir=$2
+    local path
+    local expected=0
+    local missing=0
+
+    while IFS= read -r path; do
+        [ -z "$path" ] && continue
+        expected=$((expected + 1))
+        if [ -f "$install_dir/$path" ]; then
+            pass "${profile} profile component installed: $path"
+        else
+            fail "${profile} profile component missing: $path"
+            missing=$((missing + 1))
+        fi
+    done < <(profile_component_paths "$profile")
+
+    if [ "$expected" -eq 0 ]; then
+        fail "${profile} profile has no declared components to verify"
+    elif [ "$missing" -eq 0 ]; then
+        pass "${profile} profile closure installed"
+    fi
+
+    if [ ! -e "$install_dir/config/agent-metadata.json" ]; then
+        pass "${profile} profile omitted retired agent metadata sidecar"
+    else
+        fail "${profile} profile unexpectedly included agent metadata sidecar"
+    fi
+}
+
 test_essential_profile() {
     echo -e "\n${BOLD}Test: Essential Profile Installation${NC}"
     
@@ -56,30 +119,13 @@ test_essential_profile() {
     
     bash "$REPO_ROOT/install.sh" essential --install-dir="$install_dir" > "$TEST_DIR/essential.log" 2>&1
     
-    local expected_files=(
-        "agent/core/openagent.md"
-        "agent/subagents/core/task-manager.md"
-        "agent/subagents/core/documentation.md"
-        "command/context.md"
-        "command/clean.md"
-        "config/agent-metadata.json"
-        "context/core/essential-patterns.md"
-        "context/project/project-context.md"
-    )
-    
-    local missing=0
-    for file in "${expected_files[@]}"; do
-        if [ -f "$install_dir/$file" ]; then
-            pass "Found: $file"
-        else
-            fail "Missing: $file"
-            missing=$((missing + 1))
-        fi
-    done
-    
-    if [ $missing -eq 0 ]; then
-        pass "Essential profile: all expected files present"
+    if [ -f "$install_dir/agent/core/openagent.md" ]; then
+        pass "Essential profile installed canonical OpenAgent"
+    else
+        fail "Essential profile missing canonical OpenAgent"
     fi
+
+    test_profile_closure "essential" "$install_dir"
 }
 
 test_developer_profile() {
@@ -89,53 +135,13 @@ test_developer_profile() {
     
     bash "$REPO_ROOT/install.sh" developer --install-dir="$install_dir" > "$TEST_DIR/developer.log" 2>&1
     
-    local expected_files=(
-        "agent/core/openagent.md"
-        "agent/core/opencoder.md"
-        "agent/subagents/code/tester.md"
-        "agent/subagents/code/reviewer.md"
-        "agent/subagents/code/build-agent.md"
-        "command/commit.md"
-        "command/test.md"
-        "config/agent-metadata.json"
-        "context/core/standards/code.md"
-    )
-    
-    local found=0
-    for file in "${expected_files[@]}"; do
-        if [ -f "$install_dir/$file" ]; then
-            found=$((found + 1))
-        fi
-    done
-    
-    if [ $found -ge 6 ]; then
-        pass "Developer profile: $found/${#expected_files[@]} key files present"
+    if [ -f "$install_dir/agent/core/openagent.md" ] && [ -f "$install_dir/agent/core/opencoder.md" ]; then
+        pass "Developer profile installed canonical OpenAgent and OpenCoder"
     else
-        fail "Developer profile: only $found/${#expected_files[@]} key files found"
+        fail "Developer profile missing canonical agent files"
     fi
-}
-
-test_agent_metadata_installed() {
-    echo -e "\n${BOLD}Test: Agent Metadata Config Installation${NC}"
-
-    local install_dir="$TEST_DIR/metadata-test/.opencode"
-
-    bash "$REPO_ROOT/install.sh" developer --install-dir="$install_dir" > "$TEST_DIR/metadata.log" 2>&1
-
-    local metadata_file="$install_dir/config/agent-metadata.json"
 
-    if [ -f "$metadata_file" ]; then
-        pass "Installed agent metadata config"
-    else
-        fail "Missing agent metadata config"
-        return
-    fi
-
-    if command -v jq &> /dev/null && jq -e '.agents.openagent.name == "OpenAgent"' "$metadata_file" > /dev/null; then
-        pass "Agent metadata file contains expected agent entries"
-    else
-        fail "Agent metadata file missing expected OpenAgent entry"
-    fi
+    test_profile_closure "developer" "$install_dir"
 }
 
 test_custom_install_dir() {
@@ -289,7 +295,6 @@ main() {
     test_help_and_list
     test_essential_profile
     test_developer_profile
-    test_agent_metadata_installed
     test_custom_install_dir
     test_skip_existing_files
     test_file_content_validity

+ 4 - 4
scripts/tests/test-non-interactive.sh

@@ -19,7 +19,7 @@ NC='\033[0m'
 
 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
 REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
-TEST_DIR="/tmp/opencode-noninteractive-test-$$"
+TEST_DIR="$(mktemp -d "${TMPDIR:-/tmp}/opencode-noninteractive-test.XXXXXX")"
 PASSED=0
 FAILED=0
 
@@ -81,10 +81,10 @@ test_fresh_install_piped() {
     if echo "" | bash "$REPO_ROOT/install.sh" essential --install-dir="$install_dir" 2>&1 | grep -q "Installation complete"; then
         if [ -d "$install_dir" ]; then
             pass "Fresh install completed successfully via pipe"
-            if [ -f "$install_dir/config/agent-metadata.json" ]; then
-                pass "Fresh piped install included agent metadata config"
+            if [ ! -e "$install_dir/config/agent-metadata.json" ]; then
+                pass "Fresh piped install omitted retired agent metadata sidecar"
             else
-                fail "Fresh piped install missing agent metadata config"
+                fail "Fresh piped install unexpectedly included agent metadata sidecar"
             fi
         else
             fail "Install reported success but directory not created"