---
url: https://bishop.agentdeployment.co/running/harness.md
description: >-
  Choosing between the Claude and Codex harnesses, model credentials, and model
  and effort settings.
---

# Harness and Model

The harness is what runs the agent. `claude` is the default and runs the Claude Agent SDK. `codex` runs OpenAI's Codex SDK. Both come with Bishop, so there's no separate agent CLI to install.

```json
{
  "agent": {
    "harness": "codex"
  }
}
```

`--harness` overrides it for one run, and an unsupported name fails at startup naming the ones that exist:

```
$ bishop --harness codex
$ bishop --harness gemini
unknown harness "gemini"; supported: claude, codex
```

## Model credentials

Bishop manages no model credentials. Each harness authenticates however its own CLI does in that directory, so make `claude` or `codex` work there first.

* **Claude** uses `ANTHROPIC_API_KEY` or `CLAUDE_CODE_OAUTH_TOKEN` if one is set, and otherwise whatever login `claude` has on that machine. Bishop logs which it used at startup.
* **Codex** uses `codex login`, or `CODEX_API_KEY` in the environment or `.env`. Bishop warns at startup when it can find neither, since the first turn would otherwise fail on authentication with nothing to explain it.

`OPENAI_API_KEY` isn't read, because `codex` doesn't read it either. With only `OPENAI_API_KEY` set and no login, `codex` sends no credential at all and the request comes back `401 Missing bearer`. Picking it up would also override a working `codex login` and move billing to the API key without saying so.

## Switching harness

A session belongs to the harness that started it, so switching leaves running threads unable to resume. Bishop notices, logs it, and starts each thread a fresh session on its next message rather than failing the turn. Nothing else is lost.

Codex threads read differently in one way that shows: their tool cards are vaguer, because a command comes through as a raw shell line and nothing else, which Bishop won't put in a thread.

## Model and effort

Every harness has a model and a reasoning level, so both sit at the top of `agent` rather than inside `agent.options`:

```json
{
  "agent": {
    "harness": "codex",
    "model": "gpt-5.6-codex",
    "effort": "high"
  }
}
```

Both are free strings, passed to the harness as written. The names belong to the harness and change with it, so Bishop doesn't check them: `claude` takes `claude-sonnet-5` and `low` through `max`, `codex` takes `gpt-5.6-codex` and `modelReasoningEffort`'s levels.

Nothing checks the value before the first turn. A name the harness doesn't know surfaces in a thread instead, differently by harness. Codex fails the turn with the API's own complaint about the field. Claude answers a bad model with the agent saying in prose that the model is wrong, and ignores an effort level it doesn't recognize. So an effort typo under Claude is silent: check the log line at startup, which names the model and effort Bishop passed.

`--model` and `--effort` override the config for one run:

```sh
bishop --harness claude --model claude-opus-5 --effort high
```

Setting the same thing again in `agent.options` loses to these, and startup logs a warning naming the key it overrode. [`! model`](/interfaces/commands#model) changes them for one conversation.

## Agent options

`agent.options` is passed to the harness unvalidated. Under Claude it's the Agent SDK's own options object, not a settings file: anything from [Claude Code's settings reference](https://code.claude.com/docs/en/settings-reference) goes in the nested `settings` object, which is shaped like a `.claude/settings.json`. A settings key written at the top level is dropped by the SDK without a word, so put it a level down:

```json
{
  "agent": {
    "options": {
      "maxTurns": 30,
      "settings": {
        "permissions": { "allow": ["Bash(python3:*)"] }
      }
    }
  }
}
```

These layer over the agent's own settings files rather than replacing them, so a project that sets `model` in `.claude/settings.json` keeps it unless `agent.options` names `model` too.

Bishop sets `cwd`, `resume`, `systemPrompt`, `abortController`, `sessionId`, `forkSession`, and `canUseTool` itself and ignores them here, logging a warning for each. They decide which agent runs, which conversation it continues, and how Bishop tells the agent who it's talking to.

Bishop sets two defaults, both overridable:

* **`permissionMode: "auto"`.** Nothing in a conversation can answer a permission prompt, and an agent needing approval for a tool reports itself blocked instead of doing the work. Override it if you want something stricter.
* **`settings.autoMemoryEnabled: false`.** Auto memory writes to `~/.claude/projects/<agent-directory>/memory`, outside the agent's own repository, so it doesn't travel when you deploy the agent somewhere else, and a git-URL agent's directory changes with every commit. Turn it back on with `"settings": { "autoMemoryEnabled": true }`. Bishop warns if you put it at the top of `agent.options`, where the harness ignores it.

`hooks` and `settings` merge instead of being replaced. Your hooks run alongside the one [worktree mode](/running/worktrees) installs, and your `settings` object keeps Bishop's defaults for the keys it doesn't name. Pointing `settings` at a settings file instead opts out of both defaults, which Bishop warns about.

### Under Codex

`agent.options` takes Codex's own fields instead:

```json
{
  "agent": {
    "harness": "codex",
    "options": {
      "sandboxMode": "danger-full-access",
      "additionalDirectories": ["/srv/data"]
    }
  }
}
```

Bishop's defaults are `approvalPolicy: "never"`, `sandboxMode: "workspace-write"`, `networkAccessEnabled: true`, and `skipGitRepoCheck: true`. Nothing in a conversation can answer an approval prompt, so an agent that asks for one stalls the turn. The sandbox is the compensation: writes stay inside the working directory, and network access stays on because an agent that can't push, install, or fetch fails at something in most threads. Override any of them here.

Bishop sets `workingDirectory` and `developer_instructions` itself and ignores them here. `config` and `configOverrides` reach Codex's own `--config` mechanism, which is how anything in `config.toml` can be set per agent. A `config` object you set is merged with Bishop's rather than replacing it.

## One config for both harnesses

`agent.options` is written in one harness's vocabulary, so a config meant to run either one keeps a block per harness:

```json
{
  "agent": {
    "harness": "claude",
    "worktrees": true,
    "effort": "high",

    "harnesses": {
      "claude": {
        "options": { "maxTurns": 30 }
      },
      "codex": {
        "model": "gpt-5.6-codex",
        "options": { "sandboxMode": "danger-full-access" }
      }
    }
  }
}
```

`bishop` runs Claude with its block, and `bishop --harness codex` runs Codex with its own model and sandbox setting.

A block takes `model`, `effort`, and `options`. `model` and `effort` layer over the ones beside `harness`, so `effort` above applies to both harnesses and Codex adds a model. `options` doesn't layer: it replaces `agent.options` outright, since merging two objects written in different vocabularies would splice one harness's keys into the other's call.

`agent.options` is then the options for whichever harness has no block of its own. Set both for the same harness and startup warns that the block wins. Switch to a harness with no block and startup warns that it's being handed options meant for the other one. A harness name Bishop doesn't run fails at startup, so a misspelled block can't quietly do nothing.
