Selaa lähdekoodia

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 viikkoa sitten
vanhempi
sitoutus
5c7d4cc840
53 muutettua tiedostoa jossa 695 lisäystä ja 2041 poistoa
  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
 ```yaml
 ---
 ---
-description: "Code review agent with security focus"
+name: MyAgent
+description: "Does one focused task"
 mode: subagent
 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
 ```yaml
 ---
 ---
 name: TestEngineer
 name: TestEngineer
-description: Test authoring and TDD agent
+description: "Test authoring and TDD agent"
 mode: subagent
 mode: subagent
 temperature: 0.1
 temperature: 0.1
-tools:
-  read: true
-  grep: true
-  edit: true
-  write: true
-  bash: true
-  task: true
 permission:
 permission:
   bash:
   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
 # 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
 # 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
 ```markdown
 ---
 ---
-name: AgentName
-description: Brief description
+name: MyAgent
+description: "Brief description"
 mode: subagent
 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
 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
 ## What NOT to Do
 
 
 - ❌ **Don't skip ContextScout** — build validation without project standards = running wrong commands
 - ❌ **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
 - ❌ **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>
   <context_first>ContextScout before any validation — understand project conventions first</context_first>
   <detect_first>Language detection before any commands — never assume</detect_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>
   <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)
 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
 ## Workflow
 
 
 ### Step 1: Read Subtask JSON
 ### 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
 - 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
 ## Principles
 
 
 - Context first, code second. Always.
 - 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
 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
 ## What NOT to Do
 
 
 - ❌ **Don't skip ContextScout** — reviewing without project standards = generic feedback that misses project-specific issues
 - ❌ **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
 - ❌ **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>
   <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>
   <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>
   <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
 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]
    - ✅ Positive: [expected success outcome]
    - ❌ Negative: [expected failure/edge case handling]
    - ❌ Negative: [expected failure/edge case handling]
    - ✅ Positive: [expected success outcome]
    - ✅ 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
 - ❌ **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>
   <context_first>ContextScout before any test writing — conventions matter</context_first>
   <tdd_mindset>Think about testability before implementation — tests define behavior</tdd_mindset>
   <tdd_mindset>Think about testability before implementation — tests define behavior</tdd_mindset>
   <deterministic>Tests must be reliable — no flakiness, no external dependencies</deterministic>
   <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>
 <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
 ## 📋 Process Flow
@@ -258,10 +254,6 @@ oac:
 </process_flow>
 </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">
   <parameter name="request_type" type="enum">
     Type of context management request:
     Type of context management request:
     - "discover": Discover and map context structure
     - "discover": Discover and map context structure
@@ -388,10 +380,6 @@ oac:
 </output_specification>
 </output_specification>
 
 
 ---
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
   <pre_execution>
   <pre_execution>
     - Verify request_type is valid
     - Verify request_type is valid
     - Verify scope exists or is "all"
     - Verify scope exists or is "all"
@@ -450,10 +438,6 @@ oac:
 </context_management_principles>
 </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
 ## 📝 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!
 **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}
 **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)
 ### 🎯 Primary Results (Must Read)
@@ -322,10 +318,6 @@ Always structure your response in this format:
 - {what to do with this information}
 - {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)
 ### 📚 Secondary Results (Should Read)
@@ -341,10 +333,6 @@ Always structure your response in this format:
 **Why Read This**: {brief explanation of value}
 **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
 ## 📋 Summary
@@ -404,10 +392,6 @@ read(filePath="{discovered-standards-file}")
 **Files Searched**: 12
 **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
     └── best-practices.md ⭐ FOUND
 ---
 ---
 
 
@@ -436,10 +420,6 @@ read(filePath="{discovered-standards-file}")
 - Follow naming conventions for consistency
 - 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
 ### 📍 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
 ### 📋 Summary
@@ -611,20 +587,12 @@ Repository lacks structured context
 **Files Searched**: 0
 **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
 ### 📍 Context Structure Discovered
 {show structure}
 {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})
 ### 📍 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
 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
 ## 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
 - ❌ **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>
   <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>
   <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>
   <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>
 </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">
   <tier level="1" desc="Critical Operations">
     - @check_cache_first: Check .tmp/external-context/ before fetching
     - @check_cache_first: Check .tmp/external-context/ before fetching
     - @tool_usage: Use ONLY allowed tools
     - @tool_usage: Use ONLY allowed tools
@@ -273,10 +269,6 @@ oac:
 </workflow_execution>
 </workflow_execution>
 
 
 ---
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 ---
 
 
 ## Quick Reference
 ## 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
 **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
     ├── cloudflare-deployment.md
     ├── server-functions.md
     ├── server-functions.md
     └── file-routing.md
     └── file-routing.md
@@ -328,10 +316,6 @@ If Context7 API fails:
 3. Suggest checking `.opencode/context/` for cached docs
 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
 ## Success Criteria
@@ -351,7 +335,3 @@ You succeed when ALL of these are complete:
 - Return summary without file locations
 - 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
 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
 ## 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
 - ❌ **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>
   <pre_flight>
     - ContextScout called and standards loaded
     - ContextScout called and standards loaded
     - Parent agent requirements clear
     - 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)
 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
 ## Workflow
@@ -157,10 +153,6 @@ task(subagent_type="ContextScout", description="Find frontend design standards",
 4. Present: "Updated design saved. Previous version preserved."
 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>
 <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
 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
 ## 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
 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
 ## 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
 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
 ## 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
 - ❌ **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 -->
   <!-- Context system operations routed from /context command -->
   <operation name="harvest">
   <operation name="harvest">
     Load: .opencode/context/core/context-system/operations/harvest.md
     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
 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
 ## 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
 - ❌ **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>
   <simple_pattern>
     Linear execution with validation:
     Linear execution with validation:
     1. Validate inputs → 2. Execute main task → 3. Validate outputs → 4. Deliver results
     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"
     "notify"
   ],
   ],
   "config": [
   "config": [
-    "agent-metadata",
     "env-example",
     "env-example",
     "readme"
     "readme"
   ]
   ]

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

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

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

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

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

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

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

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

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

@@ -1,5 +1,7 @@
 # OAC Canonical Refactor — Master Spec Index
 # 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.
 > **Status:** Specification — v2 (post spec-pass reconciliation). No implementation yet.
 > **Goal:** Re-architect OAC from an OpenCode-specific project into a tool-agnostic,
 > **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
 > 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
 # 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
 > **Owner:** Agent A
 > **Status:** Spec only — no implementation. **v2** (2026-07-15): census re-verified from
 > **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}`;
 > 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**
 # 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.
 > **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`,
 > Zod blocks are *illustrative*: they define the target shape of `packages/core/src/schema/*.ts`,
 > not code to ship as-is.
 > 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**
 # 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
 > **Status:** Specification only — no implementation code. **v2** (realigned to Option A
 > permissions + MVI context metadata, per `00-INDEX.md` v2).
 > permissions + MVI context metadata, per `00-INDEX.md` v2).
 > **Scope:** For each of the 4 build targets (OpenCode, Claude Code, Cursor, Windsurf), the
 > **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
 # 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
 > **Owner:** Agent D
 > **Status:** Specification (no implementation)
 > **Status:** Specification (no implementation)
 > **Scope:** The `oac` npm CLI, the neutral-source → per-tool **build pipeline**, full
 > **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
 # 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
 > **Owner:** Agent E
 > **Status:** Spec only — no implementation code. **v2** (2026-07-15): the false "160 agents"
 > **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
 > 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
 # 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`.
 > **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
 > **Method:** every load-bearing claim re-derived from the working tree. Where I could not
 > verify, I say "unverifiable" rather than guess.
 > verify, I say "unverifiable" rather than guess.

+ 2 - 0
docs/maintenance/REGISTRY_FIXES_SUMMARY.md

@@ -1,5 +1,7 @@
 # Installer and Registry Fixes Summary
 # 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
 ## Overview
 This document summarizes the fixes made to resolve installer issues and registry inconsistencies.
 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
 # 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.
 This document describes the comprehensive validation system to prevent registry inconsistencies and ensure the installer works correctly.
 
 
 ## Overview
 ## Overview
@@ -193,7 +195,7 @@ npx husky add .husky/pre-commit "bun run scripts/registry/check-dependencies.ts"
 **Solution:**
 **Solution:**
 1. Check if the dependency was renamed or removed
 1. Check if the dependency was renamed or removed
 2. Update the component's dependencies
 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
 ### 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())`
  * - **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).
  *   (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
  *   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(
  * - **The permission vocabulary is OPEN.** `Permission` is a `z.object({...}).catchall(
  *   PermissionRule).or(PermissionAction)` (config.ts:553-580), and `skill` (config.ts:575) and
  *   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;
   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
  * Infer agent ID from file path
  * @param filePath - Path to agent file
  * @param filePath - Path to agent file
@@ -240,11 +202,11 @@ function inferAgentId(filePath: string): string {
  * Loads and parses OpenAgent files from the filesystem
  * Loads and parses OpenAgent files from the filesystem
  */
  */
 export class AgentLoader {
 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
    * Load and parse a single agent file
@@ -281,12 +243,9 @@ export class AgentLoader {
       throw error;
       throw error;
     }
     }
 
 
-    // Load extended metadata from agent-metadata.json
     const agentId = inferAgentId(filePath);
     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"] = {
     const metadata: OpenAgent["metadata"] = {
       id: agentId,
       id: agentId,
       name: validatedFrontmatter.name,
       name: validatedFrontmatter.name,
@@ -294,7 +253,6 @@ export class AgentLoader {
       author: "opencode",
       author: "opencode",
       tags: [],
       tags: [],
       dependencies: [],
       dependencies: [],
-      ...extendedMetadata,
     };
     };
 
 
     // Extract sections from markdown body
     // Extract sections from markdown body
@@ -339,7 +297,7 @@ export class AgentLoader {
           await scanDirectory(fullPath);
           await scanDirectory(fullPath);
         } else if (entry.isFile() && entry.name.endsWith(".md")) {
         } else if (entry.isFile() && entry.name.endsWith(".md")) {
           try {
           try {
-            const agent = await new AgentLoader(this.projectRoot).loadFromFile(fullPath);
+            const agent = await new AgentLoader().loadFromFile(fullPath);
             agents.push(agent);
             agents.push(agent);
           } catch (error) {
           } catch (error) {
             if (error instanceof AgentLoadError) {
             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.
  * 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 {
 export class CanonicalAgentLoader {
   private readonly contentRoot: string;
   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
  * 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
  * 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";
 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.
  * 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".
  * 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
  * `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 ────────────────────────────────────────────────
  * ─── 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 ───────────────────────────────────
  * ─── 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 ─────────────────────────────────
  * ─── 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 /
  * --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:
  * 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
  *   - 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.
  *     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
  *   - `metadata.lastUpdated` is carried from the base, never stamped from the clock. The bash
@@ -86,10 +79,11 @@
  *     every rebuild a diff.
  *     every rebuild a diff.
  */
  */
 
 
-import { readFileSync } from "node:fs";
+import { existsSync, readFileSync, readdirSync } from "node:fs";
 import { resolve } from "node:path";
 import { resolve } from "node:path";
 import { CanonicalAgentLoader, type CanonicalAgentFile } from "./AgentLoader.js";
 import { CanonicalAgentLoader, type CanonicalAgentFile } from "./AgentLoader.js";
 import { generatedPaths } from "./BuildManifest.js";
 import { generatedPaths } from "./BuildManifest.js";
+import { ProfileLoader, type SystemProfileResolution } from "./ProfileLoader.js";
 
 
 // ============================================================================
 // ============================================================================
 // TYPES
 // TYPES
@@ -123,6 +117,11 @@ export interface RegistryDocument {
   [key: string]: unknown;
   [key: string]: unknown;
 }
 }
 
 
+interface RegistryProfile {
+  components?: string[];
+  [key: string]: unknown;
+}
+
 export interface RegistryEmitterOptions {
 export interface RegistryEmitterOptions {
   /** Root of the canonical agent tree, relative to the repo root. */
   /** Root of the canonical agent tree, relative to the repo root. */
   contentRoot?: string;
   contentRoot?: string;
@@ -151,6 +150,12 @@ const DEFAULTS = {
   registryFile: "registry.json",
   registryFile: "registry.json",
 } as const;
 } 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. */
 /** `oac.type` -> the `components` key its entries live under. */
 const CATEGORY_FOR_TYPE: Readonly<Record<string, string>> = {
 const CATEGORY_FOR_TYPE: Readonly<Record<string, string>> = {
   agent: "agents",
   agent: "agents",
@@ -162,6 +167,10 @@ function compare(a: string, b: string): number {
   return a < b ? -1 : a > b ? 1 : 0;
   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
 // 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
  * `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
  * 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 {
 export function entryForAgent(agent: CanonicalAgentFile, agentInstallRoot: string): RegistryEntry {
   return {
   return {
@@ -276,10 +284,10 @@ export class RegistryEmitter {
   /**
   /**
    * Generate the registry document.
    * 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> {
   async emit(): Promise<RegistryDocument> {
     const base = this.base();
     const base = this.base();
@@ -295,7 +303,9 @@ export class RegistryEmitter {
     for (const category of Object.keys(base.components)) {
     for (const category of Object.keys(base.components)) {
       const existing = base.components[category] ?? [];
       const existing = base.components[category] ?? [];
       const owned = generated.get(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
     // 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;
     const document: RegistryDocument = {} as RegistryDocument;
     for (const key of Object.keys(base)) {
     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;
     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. */
   /** The generated registry document, serialised exactly as the committed file is written. */
   async emitJson(): Promise<string> {
   async emitJson(): Promise<string> {
     return serializeRegistry(await this.emit());
     return serializeRegistry(await this.emit());
@@ -335,6 +355,84 @@ export class RegistryEmitter {
 
 
     return byCategory;
     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.
  * All Zod schemas and TypeScript types for OpenAgents Control.
  * 
  * 
  * Includes:
  * Includes:
- * - OpenAgent, AgentFrontmatter, AgentMetadata
+ * - OpenAgent, AgentFrontmatter
  * - ToolAccess, PermissionRule, GranularPermission
  * - ToolAccess, PermissionRule, GranularPermission
  * - ContextReference, DependencyReference
  * - ContextReference, DependencyReference
  * - ModelIdentifier, TemperatureSchema
  * - 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.
  * 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`
  * Output is byte-stable and a fixed point over itself, so `oac build && git diff --exit-code`
  * is a real drift gate.
  * 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
 export const OacIdSchema = z
   .string()
   .string()
@@ -358,9 +357,8 @@ const CATEGORY_SEGMENT = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
  * Organizational category. Mirrors the agent's directory under `.opencode/agent/`:
  * Organizational category. Mirrors the agent's directory under `.opencode/agent/`:
  * a closed root vocabulary, optionally followed by one `/`-joined sub-segment.
  * 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
  * "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.
  * 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;
  * 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
  * `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 —
  * 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
 export const BuildTargetsSchema = z
   .array(BuildTargetSchema)
   .array(BuildTargetSchema)
@@ -407,8 +405,7 @@ export const BuildTargetsSchema = z
  * - the flat typed string the corpus uses today (`"subagent:tester"`, `"context:standards-code"`)
  * - the flat typed string the corpus uses today (`"subagent:tester"`, `"context:standards-code"`)
  * - the structured {@link DependencyReferenceSchema} form
  * - 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([
 export const DependencyRefInputSchema = z.union([
   z.string().transform((value, ctx): z.infer<typeof DependencyReferenceSchema> => {
   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
  * 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.
  * Strict: an unknown key is an error, never silently dropped.
  */
  */
@@ -611,25 +607,6 @@ export const SystemProfileSchema = z
   })
   })
   .strict();
   .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
 // OpenAgent Schema
 // ============================================================================
 // ============================================================================
@@ -638,8 +615,7 @@ export const AgentMetadataSchema = z.object({
  * Complete OpenAgent schema combining frontmatter, metadata, system prompt,
  * Complete OpenAgent schema combining frontmatter, metadata, system prompt,
  * contexts, and optional sections.
  * 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({
 export const OpenAgentSchema = z.object({
   frontmatter: AgentFrontmatterSchema,
   frontmatter: AgentFrontmatterSchema,
@@ -716,7 +692,6 @@ export type OacBlockInput = z.input<typeof OacBlockSchema>;
 export type CanonicalAgent = z.infer<typeof CanonicalAgentSchema>;
 export type CanonicalAgent = z.infer<typeof CanonicalAgentSchema>;
 export type ContextProfile = z.infer<typeof ContextProfileSchema>;
 export type ContextProfile = z.infer<typeof ContextProfileSchema>;
 export type SystemProfile = z.infer<typeof SystemProfileSchema>;
 export type SystemProfile = z.infer<typeof SystemProfileSchema>;
-export type AgentMetadata = z.infer<typeof AgentMetadataSchema>;
 export type OpenAgent = z.infer<typeof OpenAgentSchema>;
 export type OpenAgent = z.infer<typeof OpenAgentSchema>;
 export type ToolConfig = z.infer<typeof ToolConfigSchema>;
 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 ───────────────────────────────────────────────────────────────
  * ─── Why this test exists ───────────────────────────────────────────────────────────────
  *
  *
  * Seeding `content/profiles/**` was a translation of the legacy hand-maintained lists, and
  * 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
  * 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
  * 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
  * 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
  * `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
  * 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 ────────────────────────────────────────────────
  * ─── Updated by subtask 10, deliberately ────────────────────────────────────────────────
  *
  *
@@ -163,6 +163,25 @@ describe("serializeRegistry", () => {
 // ============================================================================
 // ============================================================================
 
 
 describe("determinism", () => {
 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 () => {
   it("emits byte-identical output across two runs", async () => {
     expect(await emitRegistry(repoPath())).toBe(await emitRegistry(repoPath()));
     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", () => {
 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 document = await new RegistryEmitter(repoPath()).emit();
     const carried = Object.keys(COMMITTED.components).filter(
     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");
     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();
     const document = await new RegistryEmitter(repoPath()).emit();
 
 
     expect(Object.keys(document)).toEqual(Object.keys(COMMITTED));
     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]);
       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 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 () => {
   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 document = await new RegistryEmitter(repoPath()).emit();
     const advanced = document.profiles as Record<string, { components: string[] }>;
     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/*");
     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
     // `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 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();
     const document = await new RegistryEmitter(repoPath()).emit();
 
 
     for (const category of Object.keys(COMMITTED.components)) {
     for (const category of Object.keys(COMMITTED.components)) {
       const generated = byId(document, category);
       const generated = byId(document, category);
       const dropped = entries(COMMITTED, category)
       const dropped = entries(COMMITTED, category)
+        .filter((entry) => category !== "config" || entry.id !== "agent-metadata")
         .map((entry) => entry.id)
         .map((entry) => entry.id)
         .filter((id) => !generated.has(id));
         .filter((id) => !generated.has(id));
 
 
@@ -434,25 +483,26 @@ describe("withdrawal of entries the build itself generated", () => {
 // ============================================================================
 // ============================================================================
 
 
 describe("generation from the canonical tree", () => {
 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 document = await new RegistryEmitter(repoPath()).emit();
     const agents = await new CanonicalAgentLoader(repoPath("content/agents")).loadFromDirectory();
     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 () => {
   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", () => {
 describe("content/agents corpus", () => {
@@ -166,32 +166,23 @@ describe("content/agents corpus", () => {
     expect(rejected.join("\n") || "", "files rejected by CanonicalAgentSchema").toBe("");
     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(
     const dir = requireDir(
       "content/agents",
       "content/agents",
       OWED_BY,
       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(
     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", () => {
   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"
       "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 ids = listFiles(dir).flatMap((file) => {
       const { data } = matter(readFileSync(file, "utf-8"));
       const { data } = matter(readFileSync(file, "utf-8"));
       const parsed = OacBlockSchema.safeParse(data.oac);
       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);
     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", () => {
   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
     // The inverse of the corpus check: `oac:` is authoring-only. If it survives into
     // .opencode/agent/**, OpenCode rejects the file as an unknown field.
     // .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.
  * 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
  * 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.
  * does not accept its own corpus is not a schema, it is a wish.
  */
  */
 
 
 import { describe, it, expect } from "vitest";
 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 { dirname, join } from "node:path";
-import { fileURLToPath } from "node:url";
 import {
 import {
-  OacBlockFieldsSchema,
   OacBlockSchema,
   OacBlockSchema,
   CanonicalAgentSchema,
   CanonicalAgentSchema,
   OacCategorySchema,
   OacCategorySchema,
   BuildTargetsSchema,
   BuildTargetsSchema,
 } from "../../../src/types.js";
 } 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 = {
 const VALID_BLOCK = {
   id: "openagent",
   id: "openagent",
@@ -55,6 +32,54 @@ const VALID_BLOCK = {
   targets: ["opencode", "claude-code"],
   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("OacBlockSchema", () => {
   describe("valid blocks", () => {
   describe("valid blocks", () => {
     it("accepts a fully populated block", () => {
     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
 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
 ## What NOT to Do
 
 
 - ❌ **Don't skip ContextScout** — reviewing without project standards = generic feedback that misses project-specific issues
 - ❌ **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
 - ❌ **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>
   <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>
   <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>
   <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)
 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
 ## Workflow
 
 
 ### Step 1: Read Subtask JSON
 ### 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
 - 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
 ## Principles
 
 
 - Context first, code second. Always.
 - 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>
 <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
 ## 📋 Process Flow
@@ -203,10 +199,6 @@ model: sonnet
 </process_flow>
 </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">
   <parameter name="request_type" type="enum">
     Type of context management request:
     Type of context management request:
     - "discover": Discover and map context structure
     - "discover": Discover and map context structure
@@ -333,10 +325,6 @@ model: sonnet
 </output_specification>
 </output_specification>
 
 
 ---
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
   <pre_execution>
   <pre_execution>
     - Verify request_type is valid
     - Verify request_type is valid
     - Verify scope exists or is "all"
     - Verify scope exists or is "all"
@@ -395,10 +383,6 @@ model: sonnet
 </context_management_principles>
 </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
 ## 📝 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!
 **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>
 </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">
   <tier level="1" desc="Critical Operations">
     - @check_cache_first: Check .tmp/external-context/ before fetching
     - @check_cache_first: Check .tmp/external-context/ before fetching
     - @tool_usage: Use ONLY allowed tools
     - @tool_usage: Use ONLY allowed tools
@@ -222,10 +218,6 @@ model: haiku
 </workflow_execution>
 </workflow_execution>
 
 
 ---
 ---
-# OpenCode Agent Configuration
-# Metadata (id, name, category, type, version, author, tags, dependencies) is stored in:
-# .opencode/config/agent-metadata.json
-
 ---
 ---
 
 
 ## Quick Reference
 ## 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
 **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
     ├── cloudflare-deployment.md
     ├── server-functions.md
     ├── server-functions.md
     └── file-routing.md
     └── file-routing.md
@@ -277,10 +265,6 @@ If Context7 API fails:
 3. Suggest checking `.opencode/context/` for cached docs
 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
 ## Success Criteria
@@ -300,6 +284,3 @@ You succeed when ALL of these are complete:
 - Return summary without file locations
 - 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
 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]
    - ✅ Positive: [expected success outcome]
    - ❌ Negative: [expected failure/edge case handling]
    - ❌ Negative: [expected failure/edge case handling]
    - ✅ Positive: [expected success outcome]
    - ✅ 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
 - ❌ **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>
   <context_first>ContextScout before any test writing — conventions matter</context_first>
   <tdd_mindset>Think about testability before implementation — tests define behavior</tdd_mindset>
   <tdd_mindset>Think about testability before implementation — tests define behavior</tdd_mindset>
   <deterministic>Tests must be reliable — no flakiness, no external dependencies</deterministic>
   <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 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/register-component.sh` - Register a new component in the registry
 - `registry/validate-component.sh` - Validate component structure and metadata
 - `registry/validate-component.sh` - Validate component structure and metadata
 - `registry/validate-registry.sh` - Validate all registry paths
 - `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
 ### Registry Management
 
 
 ```bash
 ```bash
-# Auto-detect new components
+# Inspect non-canonical .opencode components (does not generate canonical agents)
 ./scripts/registry/auto-detect-components.sh --dry-run
 ./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
 ./scripts/registry/auto-detect-components.sh --auto-add
 
 
 # Validate registry
 # Validate registry

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

@@ -2,9 +2,11 @@
 
 
 #############################################################################
 #############################################################################
 # Auto-Detect Components Script v2.0.0
 # 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
 set -e
@@ -366,7 +368,7 @@ extract_metadata_from_file() {
     local tags=""
     local tags=""
     local dependencies=""
     local dependencies=""
     
     
-    # Generate ID from filename first (needed for metadata lookup)
+    # Generate the component ID from its filename.
     local filename
     local filename
     filename=$(basename "$file" .md)
     filename=$(basename "$file" .md)
     id=$(echo "$filename" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')
     id=$(echo "$filename" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')
@@ -455,41 +457,6 @@ extract_metadata_from_file() {
     # Generate name from filename (capitalize words)
     # 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')
     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}"
     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)"
 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
 REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && 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
 PASSED=0
 FAILED=0
 FAILED=0
 
 
@@ -49,6 +49,69 @@ print_header() {
     echo -e "${NC}"
     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() {
 test_essential_profile() {
     echo -e "\n${BOLD}Test: Essential Profile Installation${NC}"
     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
     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
     fi
+
+    test_profile_closure "essential" "$install_dir"
 }
 }
 
 
 test_developer_profile() {
 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
     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
     else
-        fail "Developer profile: only $found/${#expected_files[@]} key files found"
+        fail "Developer profile missing canonical agent files"
     fi
     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() {
 test_custom_install_dir() {
@@ -289,7 +295,6 @@ main() {
     test_help_and_list
     test_help_and_list
     test_essential_profile
     test_essential_profile
     test_developer_profile
     test_developer_profile
-    test_agent_metadata_installed
     test_custom_install_dir
     test_custom_install_dir
     test_skip_existing_files
     test_skip_existing_files
     test_file_content_validity
     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)"
 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
 REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && 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
 PASSED=0
 FAILED=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 echo "" | bash "$REPO_ROOT/install.sh" essential --install-dir="$install_dir" 2>&1 | grep -q "Installation complete"; then
         if [ -d "$install_dir" ]; then
         if [ -d "$install_dir" ]; then
             pass "Fresh install completed successfully via pipe"
             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
             else
-                fail "Fresh piped install missing agent metadata config"
+                fail "Fresh piped install unexpectedly included agent metadata sidecar"
             fi
             fi
         else
         else
             fail "Install reported success but directory not created"
             fail "Install reported success but directory not created"