# Spec 1 manifest

The Workbench standard defines a small, versioned package that prepares an AI to do a class of work. This page describes every field of a spec 1 manifest, the stable form of the standard.

The standard is open source and licensed under the Apache License 2.0. The specification, the [JSON schema](/docs/standard/schema), the [event protocol](/docs/standard/events), and the [outcomes contract](/docs/standard/outcomes) are the interoperability contract. The `wb` CLI is the reference engine, not the only permitted host.

## What the standard covers

A Workbench is a repository-owned execution recipe. It is not an agent, and it does not define a DAG, workflow, user interface, or product-level run. A host can add those things around it.

Neither spec defines setup hooks, knowledge-file semantics, orchestration, user interfaces, or registry behavior. Implementations must reject unsupported runners, runtimes, images, or integration transports explicitly.

## Package layout

```text
.workbenches/
  core/
    workbench.yml
    instructions.md
    skills/
      migrations/
        SKILL.md
```

Each immediate child directory of `.workbenches/` is one Workbench. Its manifest is named `workbench.yml`. The reference engine refuses a package anywhere else.

## A complete example

```yaml
spec: 1
version: 0.1.0

name: migrations
description: Design, review, and safely apply project migrations.

runner: opencode
model:
    id: openai/gpt-5.6-terra
    routes:
        - provider: openai
        - provider: openrouter

instructions: ./instructions.md
skills:
    - ./skills/migrations

tools:
    - cargo
    - dbctl

mcps:
    - name: database
      transport: http
      url: https://api.example.com/mcp
      headers:
          Authorization: Bearer ${DATABASE_API_TOKEN}

env:
    DATABASE_API_TOKEN:
        required: false

workspaces:
    api:
        required: true
        access: read-write

requirements:
    os: [linux, macos]
    cpu: 4
    memory_gb: 8

runtimes:
    local: {}
    docker:
        image: ghcr.io/example/migrations:0.1.0
```

## Fields

Required fields are `spec`, `version`, `name`, `runner`, `model`, `instructions`, and `runtimes`. No other top-level field is allowed.

| Field           | Type            | Required | Meaning                                                                          |
| --------------- | --------------- | -------- | -------------------------------------------------------------------------------- |
| `spec`          | integer `1`     | yes      | The manifest schema version.                                                     |
| `version`       | string          | yes      | The author's semantic release version, such as `0.1.0` or `1.2.0-rc.1`.          |
| `name`          | string          | yes      | The package name. The default alias and registry name.                           |
| `description`   | string          | no       | One line on what the Workbench is for.                                           |
| `runner`        | string          | yes      | The harness that executes the work, such as `opencode`, `pi`, or `claude-code`.  |
| `model`         | object          | yes      | The model policy. See [Model](#model).                                           |
| `runner_config` | string          | no       | A package-relative file or directory of runner-native configuration.             |
| `instructions`  | string          | yes      | A package-relative Markdown file.                                                |
| `skills`        | list of strings | no       | Package-relative skill directories. Defaults to `[]`.                            |
| `tools`         | list of strings | no       | CLI executables that must exist before a run starts. Defaults to `[]`.           |
| `mcps`          | list of objects | no       | Remote MCP servers. See [MCP servers](#mcp-servers). Defaults to `[]`.           |
| `env`           | map             | no       | Environment variable names the Workbench reads. See [Environment](#environment). |
| `workspaces`    | map             | no       | Named workspaces beyond the primary one. See [Workspaces](#workspaces).          |
| `requirements`  | object          | no       | What the environment must satisfy. See [Requirements](#requirements).            |
| `runtimes`      | map             | yes      | The runtimes the author supports, in order. See [Runtimes](#runtimes).           |

### spec and version

`spec` is the integer Workbench schema version. Engines dispatch on it, accept every spec they support, and reject any other value with `Manifest spec N is not supported by this engine; upgrade wb`. They never guess at an unknown spec.

`version` is the author's semantic release version. Source revisions and content digests remain the authority for reproducibility. The registry requires each pushed version to be higher than the last.

### Model

```yaml
runner: opencode
model:
    id: openai/gpt-5.6-terra
```

`model.id` uses `lab/model`, where `lab` is the organization that defines the model. It is not a provider selection. It must match `^[a-z0-9][a-z0-9._-]*/[A-Za-z0-9][A-Za-z0-9._:/-]*$`.

An engine resolves the model through verified model metadata and chooses the first route for which the selected runner has compatible credentials. The metadata version used for a run is part of the resolved runner configuration.

`model.routes` restricts or orders the providers that may serve the model. It is a non-empty list:

| Field      | Type   | Required | Meaning                                                                                                |
| ---------- | ------ | -------- | ------------------------------------------------------------------------------------------------------ |
| `provider` | string | yes      | A provider identifier matching `^[a-z0-9][a-z0-9._-]*$`, such as `openai` or `openrouter`.             |
| `model`    | string | no       | The provider-native model identifier, when it differs from the catalog or the model is absent from it. |

Routes must serve the same model identity. They are not fallbacks to different models.

```yaml
model:
    id: private-lab/review-model
    routes:
        - provider: internal-gateway
          model: deployment-42
runner_config: ./runner
```

An unknown model requires an explicit native model identifier for every route and a packaged `runner_config`.

The author locks the runner and model policy. A person starting a run cannot replace the runner, model, allowed provider routes, or packaged native runner configuration, so a published Workbench stays equivalent to the configuration its maintainer tested.

### runner_config

`runner_config` is a package-relative file or directory interpreted only by the selected runner adapter. Pi requires a directory. The path must stay inside the package and cannot contain symbolic links, credential files, or literal credential values. Configuration can name environment bindings declared by the manifest; values are supplied only when the Workbench runs.

### Credentials

A Workbench package never owns a person's runner credentials. Engines verify that at least one allowed route is ready before sending a model request. When a runner has a documented command-line sign-in, an engine can offer it during connection setup. An engine must never inject login commands or simulated input into a runner conversation, and must not parse, copy into package state, upload, or print provider token contents.

### Instructions and skills

`instructions` is a package-relative Markdown file. Each `skills` entry is a package-relative directory containing a portable `SKILL.md`. Adapters expose skills through the runner's native on-demand skill mechanism.

### Tools

`tools` names CLI executables that must be available in the selected runtime before a run starts. A tool on the host does not satisfy a container or sandbox runtime. Presence is checked, not version compatibility or behavior.

### MCP servers

`mcps` describes remote Streamable HTTP servers.

| Field       | Type           | Required | Meaning                                                                            |
| ----------- | -------------- | -------- | ---------------------------------------------------------------------------------- |
| `name`      | string         | yes      | Lowercase and hyphenated, matching `^[a-z0-9]+(?:-[a-z0-9]+)*$`.                   |
| `transport` | `http`         | no       | The only transport. Defaults to `http`.                                            |
| `url`       | string         | yes      | An `http://` or `https://` URL.                                                    |
| `headers`   | map of strings | no       | Request headers. Values can reference declared environment variables as `${NAME}`. |

A server whose headers reference an unset optional variable is omitted from the run. An unset required variable rejects the run. Adapters keep the reference instead of placing the secret value in generated configuration or dry-run output.

### Environment

```yaml
env:
    DATABASE_API_TOKEN:
        required: false
```

`env` maps variable names to declarations. Names match `^[A-Z][A-Z0-9_]*$`. Each declaration has one optional field, `required`, a boolean that defaults to `true`.

Environment values never appear in the manifest. The person or host starting a run supplies them.

### Workspaces

Every run has one primary workspace, chosen by whoever starts the run. It is the runner's working directory and is read-write. A Workbench can declare named workspaces when its expertise spans more repositories or directories:

```yaml
workspaces:
    api:
        required: true
        access: read-write
    schemas:
        required: false
        access: read-only
```

Names are lowercase and hyphenated, and `primary` is reserved. `required` defaults to `true` and `access` defaults to `read-only`; `access` is `read-only` or `read-write`. Declarations hold logical names and access, never machine paths.

A host binds a directory to each name for one run. The reference CLI takes repeatable `--workspace NAME=PATH` and rejects missing required bindings, undeclared names, unavailable directories, and duplicate resolved paths before launch. Runtimes expose each location as `WORKBENCH_WORKSPACE_<NAME>`, with hyphens replaced by underscores.

The local runtime checks readability or writability but cannot stop a host process from writing elsewhere. Engines must not present local access declarations as an isolation boundary.

### Requirements

`requirements` is optional, and every field in it is optional. An absent field places no constraint.

```yaml
requirements:
    os: [linux, macos]
    arch: [arm64, x64]
    cpu: 4
    memory_gb: 8
    disk_gb: 20
    gpu: false
```

| Field       | Type                                          | Meaning                                 |
| ----------- | --------------------------------------------- | --------------------------------------- |
| `os`        | non-empty list of `linux`, `macos`, `windows` | The environment runs any one of them.   |
| `arch`      | non-empty list of `x64`, `arm64`              | The environment uses any one of them.   |
| `cpu`       | positive integer                              | Minimum vCPUs.                          |
| `memory_gb` | positive number                               | Minimum memory in GiB.                  |
| `disk_gb`   | positive number                               | Minimum disk in GiB.                    |
| `gpu`       | boolean, default `false`                      | `true` means the Workbench needs a GPU. |

Requirements are checked against the selected runtime before it is prepared, so a mismatch stops the run before it spends anything. A requirement an engine cannot verify or enforce must be reported as unchecked, never as satisfied. How each runtime checks them is on [Runtimes](/docs/cli/runtimes).

### Runtimes

`runtimes` maps a provider name to its configuration. It must declare at least one runtime. Order matters: the first entry is the default.

| Provider  | Configuration                                                                                                 |
| --------- | ------------------------------------------------------------------------------------------------------------- |
| `local`   | None. Write `local: {}` or `local:`.                                                                          |
| `docker`  | `image` (required), `docker` (optional host engine binding).                                                  |
| `e2b`     | `image` (required).                                                                                           |
| `daytona` | `class` (required: `linux`, `windows`, `gpu`, or `macos`), `image` (optional in the schema, required to run). |

`image` is either a string naming a published image or a build object:

```yaml
runtimes:
    docker:
        image:
            build: ./Dockerfile.workbench
            context: .
```

`build` is a package-relative Dockerfile and `context` is package-relative, defaulting to `.`. Both must stay within the containing repository. Providers decide how to cache prepared images, but the result must be equivalent to preparing the declared input again.

The reference engine runs only the `linux` Daytona class. It creates the sandbox from the Daytona entry's own published `image`, with no fallback to another entry's image; a missing image or a build object fails and names `runtimes.daytona.image`.

A Docker entry can request the host's Docker engine:

```yaml
runtimes:
    docker:
        image: ghcr.io/example/project-workbench:0.4.0
        docker:
            engine:
                mode: host
```

`mode: host` is the only mode. The declaration is not authorization: the person starting each run must grant it, with `--allow-host-docker` in the reference CLI. Host engine access gives code in the Workbench effective administrative control of the Docker host. The image must contain the Docker CLI.

An unknown provider name is an error and must not silently fall back to the host. `runtime`, top-level `image`, and top-level `docker` are spec 0 fields and are invalid in spec 1.

## Runtime selection

The person or host starting a run can name one declared runtime. The reference CLI accepts `--runtime <name>` on `wb run`, `wb smoke`, `wb build`, and `wb create`. Without it, the first declared runtime is used. Naming an undeclared runtime is an error that lists the declared ones.

## Execution lifecycle

1. Resolve and validate the Workbench package.
2. Select the named runtime provider and prepare its environment.
3. Mount or copy the primary workspace, named workspaces, and package assets, then bind the run environment.
4. Verify every declared tool inside that environment.
5. Launch the runner and permit model requests only after preflight succeeds.

Preflight failure stops the run before spending model tokens and names the missing or incompatible tool. Durable executions collect changesets, artifacts, and links through the [outcomes contract](/docs/standard/outcomes).

## Versioning

A spec version that has shipped never changes. An addition, removal, or change of meaning requires a new spec number. Once a spec is released its parser is immutable: engines select the matching parser and keep old parsers for backward compatibility.

Spec 0 is frozen legacy. It has a single `runtime` field with top-level `image` and `docker`, and no `requirements` or `runtimes`. Engines still read it and treat its `runtime` as a one-entry runtimes map internally. Every 1.x engine reads spec 0 manifests. `wb init` writes spec 1.
