# `SuperPi.Harness`
[🔗](https://github.com/oleksandr/super-pi-beam/blob/v0.1.0/lib/super_pi/harness.ex#L1)

Pluggable agent harness specification and registry (`REQ-HARNESS-01`).

A harness is a named persona: a plugin set plus `pi.agent` defaults. Three showcase harnesses
and one safety harness ship with Super-Pi-BEAM:

| Id | Module | Distinct because |
|---|---|---|
| `super-pi-code` | `SuperPi.Harness.Code` | Mutates a repository, reversibly (checkpoints, optional auto-commit). |
| `super-pi-assistant` | `SuperPi.Harness.Assistant` | Works with a human present, reaches the web and third-party services over MCP, has no filesystem. |
| `super-pi-librarian` | `SuperPi.Harness.Librarian` | Runs with no human present (scheduler) and remembers across sessions (memory). |
| `super-pi-planner` | `SuperPi.Harness.Planner` | Cannot write. |

The ids of earlier releases (`super-pi-agent`, `-fast`, `-workspace`, `-auto`, `-interactive`) still
resolve, to `Code` or `Assistant`, but `list/0` no longer shows them.

## Declaring a harness

    defmodule MyApp.Harness.Triage do
      alias SuperPi.Plugins

      use SuperPi.Harness,
        id: "my-triage",
        name: "Triage",
        description: "Sorts incoming tickets",
        effort: "low",
        mode: "accept-edits",
        max_mode: "accept-edits",
        workspace: %{"kind" => "none"},
        tools: {:only, ["todo"]},
        prompt: "You triage tickets.",
        plugins: [Plugins.Todo, Plugins.Permissions]
    end

The macro defines `id/0`, `name/0`, `description/0`, `capabilities/0`, `extensions/1` and
`agent_defaults/1`, all overridable. Register the module with
`config :super_pi, extra_harnesses: [MyApp.Harness.Triage]`.

| Option | Meaning |
|---|---|
| `:id`, `:name`, `:description` | Required. Metadata for model listings and discovery. |
| `:plugins` | Plugin specs (`Module` or `{Module, opts}`). Order matters for `before_compact`: the first plugin that does not answer `:default` ends the chain. |
| `:effort`, `:mode`, `:model` | Defaults for `pi.agent`. See `SuperPi.Harness.Defaults`. |
| `:max_mode` | Highest `executionMode` the harness allows (`"plan"`, `"accept-edits"` or `"auto"`). `Permissions` enforces it. Defaults to `:mode`. |
| `:workspace` | `:local` (default), `:knowledge_paths` or a `"workspace"` map such as `%{"kind" => "none"}`. |
| `:tools` | `:all`, `{:only, names}` or `{:except, names}`. |
| `:prompt` | Core system prompt. |

# `agent_defaults`

```elixir
@callback agent_defaults(opts :: keyword()) :: map()
```

Default document configuration for the conversation agent

# `capabilities`
*optional* 

```elixir
@callback capabilities() :: map()
```

Machine-readable summary of the harness: id, plugins, tool loadout, workspace kind and modes.
Interfaces that must describe a harness to a client (ACP `initialize`, `workspace.metadata`,
`GET /v1/models`) call it through `capabilities/1`, which tolerates harnesses without it.

# `description`

```elixir
@callback description() :: String.t()
```

Short description for model listings and discovery

# `extensions`

```elixir
@callback extensions(opts :: keyword()) :: [module() | {module(), term()}]
```

List of SuperPi.Extension modules active in this harness

# `id`

```elixir
@callback id() :: String.t()
```

Unique string identifier for the harness (e.g., 'super-pi-code')

# `name`

```elixir
@callback name() :: String.t()
```

Human-readable name

# `capabilities`

```elixir
@spec capabilities(module()) :: map()
```

Capability summary of `harness`. A harness without `capabilities/0` gets one built from its
metadata, so external `:extra_harnesses` modules keep working.

# `default`

```elixir
@spec default() :: module()
```

Default harness for entry points that receive no harness name (Gateway, RPC, MCP, CLI).

Configure it with `config :super_pi, default_harness: "<harness id>"`. The default is
`super-pi-code`, which carries the permission and safety plugins.

# `fetch`

```elixir
@spec fetch(String.t() | module()) ::
  {:ok, module()} | {:error, {:unknown_harness, term()}}
```

Strict form of `resolve/1`. An unknown name is an error, not a silent default.

A deployment that wants a fallback can set `config :super_pi, fallback_harness: "<id>"`.

# `harness?`

Returns true if the given model name or ID corresponds to a registered harness.

# `list`

Lists the harness modules to show to users: the four built-in ones and any configured via
`:extra_harnesses`. Resolution (`resolve/1`, `harness?/1`) is wider than this list: it also
accepts the ids of earlier releases.

# `remote_extensions`

```elixir
@spec remote_extensions(module()) :: [module() | {module(), keyword()}]
```

Extensions of `harness` for a caller that is not the local user (Gateway, RPC, MCP, subagents).

`InlineBash` runs `!{...}` shell commands from user input with no permission check, so a
remote caller must not get it. No built-in harness carries it, but a harness from `:extra_harnesses` may. Use this instead of `harness.extensions([])` on those paths.

# `resolve`

Resolves a model name, alias, module, or harness ID to its corresponding Harness module.
Falls back to `default/0` if unknown.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
