# How Experimental Target Providers Handle Format Evolution in Compound Engineering

> Learn how experimental target providers handle format evolution in compound engineering with decoupled architecture. Isolate changes and maintain a stable CLI pipeline.

- Repository: [Every/compound-engineering-plugin](https://github.com/everyinc/compound-engineering-plugin)
- Tags: internals
- Published: 2026-02-19

---

**The Compound Engineering Plugin handles format evolution through a decoupled architecture where target registries, converters with hook-event maps, and writer modules isolate format changes to specific components without breaking the CLI pipeline.**

The EveryInc/compound-engineering-plugin repository treats every output format—including OpenCode, Codex, Droid, Cursor, Pi, and Gemini—as an **experimental target**. This design philosophy ensures the system remains adaptable as underlying provider specifications evolve, allowing developers to update individual conversion layers without disrupting the entire conversion pipeline.

## Decoupled Architecture Components

The plugin isolates format volatility across three distinct layers. When an experimental target provider changes its schema, developers modify only the relevant layer while the CLI and core logic remain untouched.

### Target Registry ([`src/targets/index.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/targets/index.ts))

The central registry exposes a `targets` object where each entry declares three properties: `implemented`, `convert`, and `write`. This structure allows the system to toggle targets on or off during format transitions.

Adding a new experimental provider requires a single registry entry. Setting `implemented` to `false` temporarily disables a target while its format undergoes updates, preventing broken conversions without removing code. The CLI references this registry when processing the `--to <target>` flag, making new targets automatically available to users.

### Converters with Hook-Event Maps (`src/converters/claude-to-*.ts`)

Converter modules translate Claude-Code concepts into target-specific schemas. Each converter contains a **hook-event map** (`HOOK_EVENT_MAP`) that bridges Claude hook names to provider-specific events.

In [`src/converters/claude-to-opencode.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/converters/claude-to-opencode.ts), the map includes experimental session events such as:

```typescript
PreCompact: { events: ["experimental.session.compacting"], type: "session" },

```

*Source*: line 55 in the file.

When Claude emits `PreCompact`, the converter generates a hook listening for the experimental OpenCode event `experimental.session.compacting`. If future OpenCode releases rename or remove this event, the conversion logic absorbs the change without affecting the writer module.

### Writer Modules (`src/targets/*.ts`)

Writer modules take intermediate bundles and emit final files in the provider's required directory structure. Each writer knows the exact file layout for its format—whether JSON, TOML, or custom configuration files.

When an experimental target's format changes, only the corresponding writer requires updates. The rest of the pipeline—from CLI parsing through converter logic—remains intact, minimizing the surface area of breaking changes.

## Handling Unmapped Events

The conversion pipeline includes defensive mechanisms for experimental features that lack stable mappings. When a Claude hook cannot be mapped to a target event, the system preserves this information rather than silently dropping it.

Lines 78-82 in [`src/converters/claude-to-opencode.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/converters/claude-to-opencode.ts) implement this safeguard:

```typescript
const unmappedComment = unmappedEvents.length > 0
  ? `// Unmapped Claude hook events: ${unmappedEvents.join(", ")}\n`
  : ""

```

*Source*: lines 78-81.

This logic emits a comment in the generated hooks file listing any unmapped Claude events. Developers reviewing the output can immediately identify which constructs require manual migration or updated mappings, providing a clear upgrade path as experimental formats stabilize.

## Practical Workflow for Format Updates

### Converting to Experimental Targets

The CLI provides direct access to all experimental targets through the `--to` flag:

```bash

# OpenCode (experimental)

bunx @every-env/compound-plugin install compound-engineering --to opencode

# Codex (experimental)

bunx @every-env/compound-plugin install compound-engineering --to codex

# Pi (experimental)

bunx @every-env/compound-plugin install compound-engineering --to pi

```

These commands invoke [`src/commands/install.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/commands/install.ts), which forwards the target name to the registry and executes the appropriate converter and writer chain.

### Adding New Experimental Mappings

To support a new Claude hook like `SessionPause` mapping to an upcoming OpenCode event, update the converter's hook-event map:

```typescript
SessionPause: { events: ["experimental.session.paused"], type: "session" },

```

After this change, subsequent conversions automatically include the new mapping. The writer module requires no modification because it consumes the intermediate bundle format, not the specific event names.

## Summary

- **Target registry** ([`src/targets/index.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/targets/index.ts)) centralizes provider availability through the `implemented` flag, allowing temporary disabling during format updates.
- **Converters** isolate schema translation through `HOOK_EVENT_MAP`, mapping Claude hooks to experimental provider events without touching file-writing logic.
- **Unmapped event tracking** preserves unhandled hooks as comments in generated files, preventing silent data loss during format evolution.
- **Writer modules** encapsulate file-layout knowledge, so format changes affect only the specific target's writer implementation.
- **CLI integration** automatically surfaces new targets via the registry, requiring no command-layer changes when adding or updating providers.

## Frequently Asked Questions

### What happens when an experimental target changes its event format?

When an experimental target changes its event format, you update the corresponding entry in the converter's `HOOK_EVENT_MAP` (e.g., in [`src/converters/claude-to-opencode.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/converters/claude-to-opencode.ts)). The system preserves unmapped events as comments in the generated output, allowing conversions to complete successfully while flagging items that need manual review. The writer module remains unchanged because it consumes an intermediate bundle rather than raw event names.

### How do I add support for a new Claude hook in an experimental target?

Add the mapping to the `HOOK_EVENT_MAP` in the appropriate converter file. For example, to map a new `SessionPause` hook to an OpenCode event, insert `SessionPause: { events: ["experimental.session.paused"], type: "session" }` into [`src/converters/claude-to-opencode.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/converters/claude-to-opencode.ts). The next conversion will automatically include this mapping in the generated hooks file without requiring changes to the writer or CLI.

### Where is the experimental target registry defined?

The experimental target registry is defined in [`src/targets/index.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/targets/index.ts). This file exports a `targets` object where each key represents a provider (such as `opencode`, `codex`, or `pi`) and each value contains `implemented`, `convert`, and `write` properties. The CLI references this registry to validate `--to` arguments and route conversions to the correct handler.

### Can I disable an experimental target while updating its format?

Yes. Set the `implemented` property to `false` in the target's registry entry within [`src/targets/index.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/targets/index.ts). This prevents the CLI from executing conversions for that target while you modify its converter logic or writer implementation. Once the format updates are complete and tested, toggle `implemented` back to `true` to re-enable the target.