# Understanding the Adapter-Per-Host Pattern in Ponytail: A Multi-Host Architecture Guide

> Explore the adapter-per-host pattern in Ponytail. Isolate platform logic for multi-host architectures and support diverse environments like Copilot, Grok, and Gemini with a single codebase.

- Repository: [DietrichGebert/ponytail](https://github.com/DietrichGebert/ponytail)
- Tags: architecture
- Published: 2026-09-11

---

**The adapter-per-host pattern in Ponytail isolates platform-specific integration logic into dedicated modules, allowing a single codebase to support diverse environments like Copilot, Grok, and Gemini through uniform manifests and optional lifecycle hooks.**

Ponytail, an open-source multi-host framework developed by DietrichGebert, leverages the **adapter-per-host pattern** to decouple core runtime logic from platform-specific implementations. This architectural approach enables the project to maintain a unified codebase while serving distinct host environments—ranging from IDE plugins to CLI tools—through specialized adapter modules located in `src/hosts/`.

## Core Purposes of the Adapter-Per-Host Pattern

### Isolation of Host-Specific Wiring

Each host receives its own thin adapter module that declares only the commands, skills, and lifecycle hooks that the platform actually supports. This prevents accidental registration of unsupported capabilities. For instance, the Grok adapter deliberately omits lifecycle hooks because its stdout interface is passive, ensuring that unsupported operations never reach the host runtime.

### Uniform Plug-In Interface

Despite architectural differences between hosts, every adapter exposes a consistent contract containing `manifest`, `rules`, `skills`, and optional `hooks`. The core entry point in [`ponytail-mcp/index.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-mcp/index.js) loads these adapters uniformly, treating Copilot, Qoder, OpenCode, and Gemini identically once the adapter module is resolved. This uniformity allows the core to delegate platform-specific operations without hard-coding host detection logic.

### Independent Testability and Extensibility

Because each adapter lives in its own file under `src/hosts/`, the test suite can verify host integrations independently. Files like [`tests/copilot-plugin.test.js`](https://github.com/DietrichGebert/ponytail/blob/main/tests/copilot-plugin.test.js) and [`tests/grok-plugin.test.js`](https://github.com/DietrichGebert/ponytail/blob/main/tests/grok-plugin.test.js) smoke-test manifest shapes and hook behavior without cross-host interference. Adding support for a new host requires only creating a new adapter module and corresponding test file, leaving the core untouched.

## Adapter Structure and Implementation

Adapters are lightweight JavaScript modules with three primary responsibilities: exporting a manifest that describes supported commands and skills, providing optional lifecycle hooks (`onActivate`, `onDeactivate`), and registering host-specific rules.

The following example demonstrates how Grok's adapter omits hooks entirely, as enforced by the test suite:

```javascript
// tests/grok-plugin.test.js
// Grok's adapter must not register any hooks because its stdout is passive.
test('Grok manifest is a skill‑only adapter with no lifecycle hooks', () => {
  const { hooks } = require('../../src/hosts/grok.js');
  expect(hooks).toBeUndefined();
});

```

Conversely, the Copilot adapter exposes a minimal command set verified in its own test file:

```javascript
// tests/copilot-plugin.test.js – smoke test ensures minimal command wiring
// The actual adapter lives in src/hosts/copilot.js (not shown here)
import { manifest } from '../../src/hosts/copilot.js';
expect(manifest.commands).toContain('run');

```

When adding a new host, developers create a module following this structure:

```javascript
// src/hosts/newhost.js
export const manifest = {
  commands: ['run', 'debug'],
  skills: ['codeCompletion'],
};

export const hooks = {
  onActivate() { /* host‑specific init */ },
  onDeactivate() { /* cleanup */ },
};

```

## Host-Specific Implementations in the Codebase

The Ponytail repository validates the adapter-per-host pattern through dedicated test suites that verify each integration:

- **[`tests/copilot-plugin.test.js`](https://github.com/DietrichGebert/ponytail/blob/main/tests/copilot-plugin.test.js)** confirms the Copilot adapter exposes a minimal command set through its manifest.
- **[`tests/grok-plugin.test.js`](https://github.com/DietrichGebert/ponytail/blob/main/tests/grok-plugin.test.js)** verifies that Grok's adapter returns `undefined` for hooks, preventing lifecycle calls on passive stdout hosts.
- **[`tests/qoder-plugin.test.js`](https://github.com/DietrichGebert/ponytail/blob/main/tests/qoder-plugin.test.js)** validates the Qoder CLI adapter's manifest structure and rule registrations.
- **[`tests/opencode-plugin.test.js`](https://github.com/DietrichGebert/ponytail/blob/main/tests/opencode-plugin.test.js)** checks OpenCode-specific hook behavior and activation logic.
- **[`tests/gemini-extension.test.js`](https://github.com/DietrichGebert/ponytail/blob/main/tests/gemini-extension.test.js)** ensures the Gemini CLI adapter correctly wires skills and commands.

These tests import adapter logic from [`src/hosts/copilot.js`](https://github.com/DietrichGebert/ponytail/blob/main/src/hosts/copilot.js) and [`src/hosts/grok.js`](https://github.com/DietrichGebert/ponytail/blob/main/src/hosts/grok.js), demonstrating how each host lives in its own module. The central runtime in [`ponytail-mcp/index.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-mcp/index.js) orchestrates these adapters, loading the appropriate module based on the detected host environment without embedding platform logic into the core.

## Summary

- The **adapter-per-host pattern** isolates platform-specific code into dedicated modules under `src/hosts/`.
- Each adapter exports a uniform interface (`manifest`, `skills`, optional `hooks`) that [`ponytail-mcp/index.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-mcp/index.js) consumes regardless of host type.
- Hosts with restricted capabilities (like Grok) omit unsupported features rather than implementing stubs, preventing runtime errors.
- The architecture enables independent testing via dedicated `tests/*-plugin.test.js` files for each supported platform.
- Adding new host support requires only creating a new adapter module in `src/hosts/`, with zero changes to Ponytail's core logic.

## Frequently Asked Questions

### What is the adapter-per-host pattern?

The adapter-per-host pattern is an architectural design where each supported platform (host) receives its own dedicated integration module. In Ponytail, this translates to separate files like [`src/hosts/copilot.js`](https://github.com/DietrichGebert/ponytail/blob/main/src/hosts/copilot.js) and [`src/hosts/grok.js`](https://github.com/DietrichGebert/ponytail/blob/main/src/hosts/grok.js) that convert generic framework commands into host-specific API calls, ensuring the core remains platform-agnostic.

### How does Ponytail handle hosts that don't support lifecycle hooks?

Hosts lacking lifecycle support simply omit the `hooks` export from their adapter module. As verified in [`tests/grok-plugin.test.js`](https://github.com/DietrichGebert/ponytail/blob/main/tests/grok-plugin.test.js), the Grok adapter returns `undefined` for hooks, and the core runtime in [`ponytail-mcp/index.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-mcp/index.js) checks for hook existence before invocation, gracefully skipping lifecycle management for passive stdout-based hosts.

### Where is the adapter loading logic implemented in Ponytail?

The adapter resolution and loading logic resides in [`ponytail-mcp/index.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-mcp/index.js). This core entry point detects the current host environment, dynamically imports the corresponding adapter from `src/hosts/`, and delegates all platform-specific operations to the adapter's uniform interface.

### How do I add a new host adapter to Ponytail?

Create a JavaScript file in `src/hosts/` that exports a `manifest` object declaring commands and skills, and optionally a `hooks` object containing `onActivate` and `onDeactivate` functions. Then create a corresponding test file in `tests/` (e.g., [`newhost-plugin.test.js`](https://github.com/DietrichGebert/ponytail/blob/main/newhost-plugin.test.js)) to verify the manifest shape, ensuring the adapter integrates seamlessly with the existing core without modification.