# Harnesses

How a project selects its default or experimental runtime, and which session features each supports.

Canonical page: https://beeuni.ai/docs/work/harnesses

A harness is the agent runtime inside a session's sandbox. It runs the model
loop, calls tools, and produces the transcript. A session uses either the
default runtime or the experimental runtime.

Both harnesses read the same project: `beeuni.yaml`, the agent files in
`agents/`, the skills in `skills/`, the root `AGENTS.md`, and `memory/`. The
clients (web, CLI, TUI, mobile, SDK) read both through the same session API.
The differences are the session features in the
[matrix](#what-each-harness-supports) below and the files each harness reads
for its own configuration.

## How a session selects its harness

Beeuni decides when a session starts, restarts, or resumes. A running session
keeps its harness until then.

| Rule, in order | Harness |
| --- | --- |
| The project's `llm_gateway` flag is off | default runtime |
| The project's `pi_harness` flag is on | experimental runtime |
| `beeuni.yaml` sets `runtime: pi` | experimental runtime |
| Anything else | default runtime |

- The experimental runtime calls models only through the Beeuni LLM gateway. A project with
  `llm_gateway` off uses the default runtime, whatever the flag or the manifest says.
  See [Models](/docs/project/models).
- `runtime` is a `beeuni_version: 2` manifest key. A legacy `beeuni.toml`
  project cannot set it. See [the manifest reference](/docs/project/manifest).
- `pi_harness` is a per-project feature flag, off by default. See
  [Feature flags](/docs/feature-flags).

To see which harness a session runs, read `harness.id` in the sandbox
daemon's `GET /beeuni/health` answer: `opencode` or `pi`.

## What each harness supports

A harness lists the optional session features it serves in the `capabilities`
array of `GET /beeuni/health`. A client hides a control when the session's
`capabilities` lacks the id. The SDK check is
`runtimeSupports`.

### Both harnesses

| Feature | default runtime | experimental runtime |
| --- | --- | --- |
| Chat turns, streaming, stop | Yes | Yes |
| Agent `.md`: prompt, `model`, `variant`, `mode`, `permission`, `temperature`, `top_p`, `steps` | Yes | Yes. The experimental runtime does not apply `options` (provider options). |
| [Tool access](/docs/project/tools#who-may-use-which-tool) per agent (`agents.<name>.tools` in `beeuni.yaml`: `all`, `none`, a list, `exclude`) | Yes | Yes |
| Per-agent `permission` rules and permission requests | Yes | Yes |
| Questions (`question` tool) | Yes | Yes |
| The project's root `AGENTS.md` rules file, or `CLAUDE.md` when the root has no `AGENTS.md` | Yes. The default runtime also reads the file in parent directories of `/workspace`, and reads `/workspace` while it runs a config release. | Yes, the root file only. It is part of the system prompt of the session and of each subagent. The experimental runtime reads [the release copy](#agentsmd-on-pi). |
| Subagents (`task` tool), `session.subagents` | Yes | Yes: `general`, `explore` (read-only), and each project agent with `mode: subagent` or `all`. A subagent cannot start another subagent. |
| Skills (`skills/` and the managed `beeuni-*` skills), with the agent's `skills` grant | Yes. The agent loads a skill with the `skill` tool. | Yes. The system prompt lists each granted skill; the agent reads the file. |
| [Beeuni tools](/docs/project/tools#beeuni-tools-in-beeuniyaml): `web_search`, `image_search`, `scrape_webpage`, `memory`, `show`. All five with no `tools:` key in `beeuni.yaml`; with one, the ones it lists as `beeuni:<name>` | Yes | Yes |
| [Project tools](/docs/project/tools) (`tools:` in `beeuni.yaml`) | Yes | Yes |
| [Config releases](/docs/work/runtime#config-convergence), `config.release.v1` | Yes | Yes |
| Compact (summarize) the conversation, on demand and by itself near the model's context window, `session.compact` | Yes | Yes. A model request that overflows the window compacts and retries once. |
| Project slash commands, `session.commands` | Yes, from `commands/` in the default runtime config directory. | Yes, from the prompt templates in `prompts/` in the experimental runtime config directory and in experimental runtime packages. The experimental runtime does not read the default runtime’s `commands/`. |
| Terminal in the sandbox (PTY) | Yes | Yes |
| `bash`, `read`, `write`, `edit`, `glob`, `grep` tools; [secrets](/docs/project/secrets) in the shell; the `beeuni` CLI | Yes | Yes |

The Beeuni tools and the project tools are [harness-neutral](/docs/project/tools):
the sandbox daemon runs one module per tool for both harnesses, with the same
names, arguments and output. The experimental runtime calls them in the daemon's process; default runtime
calls them through a plugin the daemon writes. Both harnesses get the same
list: a Beeuni tool the project's `tools:` key does not list is on neither. A
project that still has copies of the Beeuni tools in `harnesses/opencode/tools/`
keeps its copies on the default runtime until it deletes them.

The web tools follow the `websearch` (`web_search`, `image_search`) and
`webfetch` (`scrape_webpage`) permission keys on both harnesses. on the default runtime
these tools cannot ask: an `ask` rule denies them. on the experimental runtime an `ask` rule raises
a permission request. on the experimental runtime a `memory` command that changes a file follows
`edit` and `view` follows `read`; on the default runtime only a `memory` rule governs
the tool.

<a id="agentsmd-on-pi"></a>

### `AGENTS.md` on the experimental runtime

The experimental runtime puts the project's root `AGENTS.md` in the system prompt as one section,
`<project_instructions path="AGENTS.md">`, after the agent's prompt. A root
with no `AGENTS.md` gives `CLAUDE.md` instead, as on the default runtime. The experimental runtime reads no
`AGENTS.md` in a subdirectory or a parent directory.

- **Source.** experimental runtime reads the copy in the
  [config release](/docs/work/runtime#config-convergence) and never the one in
  `/workspace`: an edit in `/workspace` reaches the session after it merges to
  the base branch. With no release to serve (`config.source: image-default`),
  experimental runtime reads no `AGENTS.md`, as it reads no project skill.
- **When.** experimental runtime reads the file when the runtime starts, when its configuration
  reloads (a new release, a `/beeuni/env` push), and after a repository
  refresh. It does not read it before each turn. A new release reaches the
  next turn without a restart; a turn in flight keeps the old file.
- **Skipped.** An empty file, a file that is not a regular file, and a
  symbolic link that resolves outside the repository add nothing. The last
  two write one warning to the daemon log.
- **Check.** `harness.details.agentsMd` in the daemon's `GET /beeuni/health`
  names the file experimental runtime loaded: `source` (`release` or `workspace`), `path`,
  `bytes`, and `sha` (the first 12 hex characters of its sha256). It is `null`
  when experimental runtime loaded none.

<a id="opencode-only-today"></a>

### Standard harness only today

| Feature | Capability | default runtime | experimental runtime |
| --- | --- | --- | --- |
| Rewind to a message and restore it. Edit-and-resend uses rewind. | `session.rewind` | Yes | No |
| MCP servers (`mcp` in `opencode.jsonc`) | `session.mcp` | Yes | No MCP client. [Connectors](/docs/connect/connectors) work on both harnesses through the `beeuni connectors` CLI. |
| Todo list (`todowrite` tool) and the plan card | `session.todo` | Yes | No. The plan card stays empty. |
| Attach the harness's own terminal client (`beeuni connect`, TUI `Alt+o`) | `session.attach` | Yes | No. See [TUI](/docs/cli#sessions). |
| Runtime config document (`/global/config`) | `session.config` | Yes | No. Agents and models come from the project on both. |
| Fork a session at a message | `session.fork` | Yes | No |
| A shell command run as a turn | `session.shell` | Yes | No |
| Model providers without the LLM gateway | — | Yes | No. The experimental runtime needs `llm_gateway` on. |

A experimental runtime session answers a request for a feature it does not serve with
`501 feature_not_supported`, or `404` for a document it does not have
(`/global/config`). It does not ignore the request.

## Harness configuration files

Each harness has one directory for the files only that harness reads.

| | default runtime | experimental runtime |
| --- | --- | --- |
| Directory | `harnesses/opencode/`, or `opencode.config_dir` | `harnesses/pi/`, or `pi.config_dir` |
| Legacy directory | `.beeuni/opencode/` | `.beeuni/pi/` |
| Contents | `opencode.jsonc`, `plugins/`, `tools/`, `commands/` | `settings.json`, `extensions/`, `skills/`, `prompts/` |
| Extensions | Plugins and default runtime-only tools in that directory | [experimental runtime packages](/docs/project/manifest#harnesses) in `harnesses.pi.packages`, and extension files in `extensions/` |

A experimental runtime session does not read `opencode.jsonc`, `plugins/`, `tools/`, or
`commands/`. An default runtime session does not read the experimental runtime directory. A tool both
harnesses need is a [project tool](/docs/project/tools), declared in
`beeuni.yaml`, not a file in either directory.

The experimental runtime packages come from packages. A package
can add tools that experimental runtime does not have, for example an MCP adapter or a todo
tool. Beeuni loads the package; it does not test third-party packages.
