> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fluffbuzz.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent harness plugins

An **agent harness** is the low level executor for one prepared FluffBuzz agent
turn. It is not a model provider, not a channel, and not a tool registry.

Use this surface only for bundled or trusted native plugins. The contract is
still experimental because the parameter types intentionally mirror the current
embedded runner.

## When to use a harness

Register an agent harness when a model family has its own native session
runtime and the normal FluffBuzz provider transport is the wrong abstraction.

Examples:

* a native coding-agent server that owns threads and compaction
* a local CLI or daemon that must stream native plan/reasoning/tool events
* a model runtime that needs its own resume id in addition to the FluffBuzz
  session transcript

Do **not** register a harness just to add a new LLM API. For normal HTTP or
WebSocket model APIs, build a [provider plugin](/plugins/sdk-provider-plugins).

## What core still owns

Before a harness is selected, FluffBuzz has already resolved:

* provider and model
* runtime auth state
* thinking level and context budget
* the FluffBuzz transcript/session file
* workspace, sandbox, and tool policy
* channel reply callbacks and streaming callbacks
* model fallback and live model switching policy

That split is intentional. A harness runs a prepared attempt; it does not pick
providers, replace channel delivery, or silently switch models.

## Register a harness

**Import:** `fluffbuzz/plugin-sdk/agent-harness`

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
import type { AgentHarness } from "fluffbuzz/plugin-sdk/agent-harness";
import { definePluginEntry } from "fluffbuzz/plugin-sdk/plugin-entry";

const myHarness: AgentHarness = {
  id: "my-harness",
  label: "My native agent harness",

  supports(ctx) {
    return ctx.provider === "my-provider"
      ? { supported: true, priority: 100 }
      : { supported: false };
  },

  async runAttempt(params) {
    // Start or resume your native thread.
    // Use params.prompt, params.tools, params.images, params.onPartialReply,
    // params.onAgentEvent, and the other prepared attempt fields.
    return await runMyNativeTurn(params);
  },
};

export default definePluginEntry({
  id: "my-native-agent",
  name: "My Native Agent",
  description: "Runs selected models through a native agent daemon.",
  register(api) {
    api.registerAgentHarness(myHarness);
  },
});
```

## Selection policy

FluffBuzz chooses a harness after provider/model resolution:

1. An existing session's recorded harness id wins, so config/env changes do not
   hot-switch that transcript to another runtime.
2. `FLUFFBUZZ_AGENT_RUNTIME=<id>` forces a registered harness with that id for
   sessions that are not already pinned.
3. `FLUFFBUZZ_AGENT_RUNTIME=pi` forces the built-in PI harness.
4. `FLUFFBUZZ_AGENT_RUNTIME=auto` asks registered harnesses if they support the
   resolved provider/model.
5. If no registered harness matches, FluffBuzz uses PI unless PI fallback is
   disabled.

Plugin harness failures surface as run failures. In `auto` mode, PI fallback is
only used when no registered plugin harness supports the resolved
provider/model. Once a plugin harness has claimed a run, FluffBuzz does not
replay that same turn through PI because that can change auth/runtime semantics
or duplicate side effects.

The selected harness id is persisted with the session id after an embedded run.
Legacy sessions created before harness pins are treated as PI-pinned once they
have transcript history. Use a new/reset session when changing between PI and a
native plugin harness. `/status` shows non-default harness ids such as `codex`
next to `Fast`; PI stays hidden because it is the default compatibility path.
If the selected harness is surprising, enable `agents/harness` debug logging and
inspect the gateway's structured `agent harness selected` record. It includes
the selected harness id, selection reason, runtime/fallback policy, and, in
`auto` mode, each plugin candidate's support result.

The bundled Codex plugin registers `codex` as its harness id. Core treats that
as an ordinary plugin harness id; Codex-specific aliases belong in the plugin
or operator config, not in the shared runtime selector.

## Provider plus harness pairing

Most harnesses should also register a provider. The provider makes model refs,
auth status, model metadata, and `/model` selection visible to the rest of
FluffBuzz. The harness then claims that provider in `supports(...)`.

The bundled Codex plugin follows this pattern:

* provider id: `codex`
* user model refs: `openai/gpt-5.5` plus `embeddedHarness.runtime: "codex"`;
  legacy `codex/gpt-*` refs remain accepted for compatibility
* harness id: `codex`
* auth: synthetic provider availability, because the Codex harness owns the
  native Codex login/session
* app-server request: FluffBuzz sends the bare model id to Codex and lets the
  harness talk to the native app-server protocol

The Codex plugin is additive. Plain `openai/gpt-*` refs continue to use the
normal FluffBuzz provider path unless you force the Codex harness with
`embeddedHarness.runtime: "codex"`. Older `codex/gpt-*` refs still select the
Codex provider and harness for compatibility.

For operator setup, model prefix examples, and Codex-only configs, see
[Codex Harness](/plugins/codex-harness).

FluffBuzz requires Codex app-server `0.118.0` or newer. The Codex plugin checks
the app-server initialize handshake and blocks older or unversioned servers so
FluffBuzz only runs against the protocol surface it has been tested with.

### Codex app-server tool-result middleware

Bundled plugins can also attach Codex app-server-specific `tool_result`
middleware through `api.registerCodexAppServerExtensionFactory(...)` when their
manifest declares `contracts.embeddedExtensionFactories: ["codex-app-server"]`.
This is the trusted-plugin seam for async tool-result transforms that need to
run inside the native Codex harness before the tool output is projected back
into the FluffBuzz transcript.

### Native Codex harness mode

The bundled `codex` harness is the native Codex mode for embedded FluffBuzz
agent turns. Enable the bundled `codex` plugin first, and include `codex` in
`plugins.allow` if your config uses a restrictive allowlist. Native app-server
configs should use `openai/gpt-*` with `embeddedHarness.runtime: "codex"`.
Use `openai-codex/*` for Codex OAuth through PI instead. Legacy `codex/*`
model refs remain compatibility aliases for the native harness.

When this mode runs, Codex owns the native thread id, resume behavior,
compaction, and app-server execution. FluffBuzz still owns the chat channel,
visible transcript mirror, tool policy, approvals, media delivery, and session
selection. Use `embeddedHarness.runtime: "codex"` with
`embeddedHarness.fallback: "none"` when you need to prove that only the Codex
app-server path can claim the run. That config is only a selection guard:
Codex app-server failures already fail directly instead of retrying through PI.

## Disable PI fallback

By default, FluffBuzz runs embedded agents with `agents.defaults.embeddedHarness`
set to `{ runtime: "auto", fallback: "pi" }`. In `auto` mode, registered plugin
harnesses can claim a provider/model pair. If none match, FluffBuzz falls back
to PI.

Set `fallback: "none"` when you need missing plugin harness selection to fail
instead of using PI. Selected plugin harness failures already fail hard. This
does not block an explicit `runtime: "pi"` or `FLUFFBUZZ_AGENT_RUNTIME=pi`.

For Codex-only embedded runs:

```json theme={"theme":{"light":"min-light","dark":"min-dark"}}
{
  "agents": {
    "defaults": {
      "model": "openai/gpt-5.5",
      "embeddedHarness": {
        "runtime": "codex",
        "fallback": "none"
      }
    }
  }
}
```

If you want any registered plugin harness to claim matching models but never
want FluffBuzz to silently fall back to PI, keep `runtime: "auto"` and disable
the fallback:

```json theme={"theme":{"light":"min-light","dark":"min-dark"}}
{
  "agents": {
    "defaults": {
      "embeddedHarness": {
        "runtime": "auto",
        "fallback": "none"
      }
    }
  }
}
```

Per-agent overrides use the same shape:

```json theme={"theme":{"light":"min-light","dark":"min-dark"}}
{
  "agents": {
    "defaults": {
      "embeddedHarness": {
        "runtime": "auto",
        "fallback": "pi"
      }
    },
    "list": [
      {
        "id": "codex-only",
        "model": "openai/gpt-5.5",
        "embeddedHarness": {
          "runtime": "codex",
          "fallback": "none"
        }
      }
    ]
  }
}
```

`FLUFFBUZZ_AGENT_RUNTIME` still overrides the configured runtime. Use
`FLUFFBUZZ_AGENT_HARNESS_FALLBACK=none` to disable PI fallback from the
environment.

```bash theme={"theme":{"light":"min-light","dark":"min-dark"}}
FLUFFBUZZ_AGENT_RUNTIME=codex \
FLUFFBUZZ_AGENT_HARNESS_FALLBACK=none \
fluffbuzz gateway run
```

With fallback disabled, a session fails early when the requested harness is not
registered, does not support the resolved provider/model, or fails before
producing turn side effects. That is intentional for Codex-only deployments and
for live tests that must prove the Codex app-server path is actually in use.

This setting only controls the embedded agent harness. It does not disable
image, video, music, TTS, PDF, or other provider-specific model routing.

## Native sessions and transcript mirror

A harness may keep a native session id, thread id, or daemon-side resume token.
Keep that binding explicitly associated with the FluffBuzz session, and keep
mirroring user-visible assistant/tool output into the FluffBuzz transcript.

The FluffBuzz transcript remains the compatibility layer for:

* channel-visible session history
* transcript search and indexing
* switching back to the built-in PI harness on a later turn
* generic `/new`, `/reset`, and session deletion behavior

If your harness stores a sidecar binding, implement `reset(...)` so FluffBuzz can
clear it when the owning FluffBuzz session is reset.

## Tool and media results

Core constructs the FluffBuzz tool list and passes it into the prepared attempt.
When a harness executes a dynamic tool call, return the tool result back through
the harness result shape instead of sending channel media yourself.

This keeps text, image, video, music, TTS, approval, and messaging-tool outputs
on the same delivery path as PI-backed runs.

## Current limitations

* The public import path is generic, but some attempt/result type aliases still
  carry `Pi` names for compatibility.
* Third-party harness installation is experimental. Prefer provider plugins
  until you need a native session runtime.
* Harness switching is supported across turns. Do not switch harnesses in the
  middle of a turn after native tools, approvals, assistant text, or message
  sends have started.

## Related

* [SDK Overview](/plugins/sdk-overview)
* [Runtime Helpers](/plugins/sdk-runtime)
* [Provider Plugins](/plugins/sdk-provider-plugins)
* [Codex Harness](/plugins/codex-harness)
* [Model Providers](/concepts/model-providers)
