---
title: "Coding Agents & Harnesses"
description: "Point Claude Code, omp, Hermes and other harnesses at this API"
canonical: "https://inference.boundless.network/docs/coding-agents-and-harnesses"
last-updated: "2018-10-20T01:46:40.000Z"
---

# Coding Agents & Harnesses

Point Codex CLI, the OpenAI SDK, Hermes, OpenCode and other agent clients at the gateway.


Any agent client that lets you set a base URL and an API key works here. The API speaks [three wire formats](/docs/api-compatibility): OpenAI chat completions, the Codex-compatible Responses subset, and Anthropic Messages. The format your client uses decides which base URL you give it.

| Wire format | Base URL | Credential |
| --- | --- | --- |
| OpenAI chat completions | `https://api.inference.boundless.network/v1` | `Authorization: Bearer <key>` |
| OpenAI Responses (Codex-compatible subset) | `https://api.inference.boundless.network/v1` | `Authorization: Bearer <key>` |
| Anthropic Messages | `https://api.inference.boundless.network` | `x-api-key: <key>`, or `Authorization: Bearer <key>` |

> The Anthropic clients append `/v1/messages` themselves, which is why that row takes the root while the OpenAI row takes `/v1`.

## Set up your harness with Boundless

### Claude Code

Anthropic Messages format. Put the configuration in `~/.claude/settings.json` so it applies everywhere Claude Code runs. If Claude Code is already open, exit it before changing this file; start a fresh session afterward rather than resuming one that used an Anthropic model.

```json
{
  "model": "glm-5.2",
  "env": {
    "ANTHROPIC_BASE_URL": "https://api.inference.boundless.network",
    "ANTHROPIC_AUTH_TOKEN": "your-boundless-key",
    "ANTHROPIC_MODEL": "glm-5.2",
    "ANTHROPIC_DEFAULT_OPUS_MODEL": "glm-5.2",
    "ANTHROPIC_DEFAULT_SONNET_MODEL": "glm-5.2",
    "ANTHROPIC_DEFAULT_HAIKU_MODEL": "dsv4",
    "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
  }
}
```

- **Use `ANTHROPIC_AUTH_TOKEN` rather than `ANTHROPIC_API_KEY`.** The first sends your key as `Authorization: Bearer` and takes effect immediately; the second sends it as `x-api-key` and has to be approved once in an interactive session first.
- **Set every model slot.** Claude Code can select its saved model or a family model for foreground and background work. Every slot has to name a model this gateway serves.
- **The credential replaces your claude.ai login** for as long as this file sets it. Remove it from `settings.json` to go back to your subscription — an `env` block beats a shell export, so unsetting the variable in your terminal changes nothing while the file still names it. To keep the key out of the file entirely, drop `ANTHROPIC_AUTH_TOKEN` and use [`apiKeyHelper`](https://code.claude.com/docs/en/settings-reference) instead, which reads the credential from a command you supply.

```bash
claude --model "glm-5.2" -p 'Use the Bash tool to run printf boundless, then report the output.'
```

### OpenAI SDK

The official SDK works unchanged against the chat-completions endpoint. This example forces one tool call, assembles its streamed arguments, returns the local result, then streams the final answer. The last chunk of each request carries its exact token counts.

```python
import json
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["BOUNDLESS_API_KEY"],
    base_url="https://api.inference.boundless.network/v1",
)
messages = [{"role": "user", "content": "What is 20 + 22? Use the add tool."}]
tools = [{
    "type": "function",
    "function": {
        "name": "add",
        "description": "Add two integers.",
        "parameters": {
            "type": "object",
            "properties": {
                "a": {"type": "integer"},
                "b": {"type": "integer"},
            },
            "required": ["a", "b"],
            "additionalProperties": False,
        },
    },
}]

stream = client.chat.completions.create(
    model="qwen3.6",
    messages=messages,
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "function", "function": {"name": "add"}},
    stream=True,
    stream_options={"include_usage": True},
)

tool_call = {"id": "", "type": "function", "function": {"name": "", "arguments": ""}}
for chunk in stream:
    if chunk.usage:
        print(f"first request: {chunk.usage}")
    if not chunk.choices:
        continue
    for delta_call in chunk.choices[0].delta.tool_calls or []:
        if delta_call.id:
            tool_call["id"] = delta_call.id
        if delta_call.function.name:
            tool_call["function"]["name"] = delta_call.function.name
        if delta_call.function.arguments:
            tool_call["function"]["arguments"] += delta_call.function.arguments

arguments = json.loads(tool_call["function"]["arguments"])
messages.extend([
    {"role": "assistant", "tool_calls": [tool_call]},
    {
        "role": "tool",
        "tool_call_id": tool_call["id"],
        "content": json.dumps({"result": arguments["a"] + arguments["b"]}),
    },
])

stream = client.chat.completions.create(
    model="qwen3.6",
    messages=messages,
    max_tokens=1024,
    tools=tools,
    stream=True,
    stream_options={"include_usage": True},
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
    if chunk.usage:
        print(f"\nfinal request: {chunk.usage}")
```

### omp (Oh My Pi)

Declare a provider in `~/.omp/agent/models.yml`:

```yaml
providers:
  boundless:
    baseUrl: https://api.inference.boundless.network/v1
    api: openai-completions
    apiKey: BOUNDLESS_API_KEY
    discovery:
      type: litellm
    compat:
      reasoningContentField: reasoning_content

modelRoles:
  default: boundless/glm-5.2
  tiny: boundless/dsv4
```

No model list, and no prices: `discovery` fetches both. omp reads `GET /v1/model/info` at startup and takes each model's id, context window, rates — input, output and cached input — and whether it does tool calls, vision or reasoning straight off the wire. A model we add appears in the picker, priced, without an edit here, and a price change reaches you on the next start rather than the next time somebody remembers this page.

Only `modelRoles` names a model, so switching later is a role change rather than a new entry. `default` is the model omp runs; `tiny` is the cheap one it uses for summaries and titles.

`compat` carries the one fact no endpoint reports: reasoning arrives as `reasoning_content`. Everything else — including whether a model takes `reasoning_effort` — omp learns from the discovery response.

`apiKey` is read as the *name* of an environment variable before it is treated as a literal token, so the value above keeps your key out of the file.

### Hermes

OpenAI format, in `~/.hermes/config.yaml`. `key_env` names the variable to read, so the key stays out of the file:

```yaml
providers:
  boundless:
    api: https://api.inference.boundless.network/v1
    key_env: BOUNDLESS_API_KEY
    transport: chat_completions
    models:
      qwen3.6:
        context_length: 262144

model:
  provider: custom:boundless
  default: qwen3.6
```

```bash
hermes chat --toolsets terminal -q 'Use the terminal tool to run printf boundless, then report the output.'
```

### OpenCode

A custom provider in `opencode.json`, either the one in your project or `~/.config/opencode/opencode.json` for every project:

```json
{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "boundless": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "Boundless",
      "options": {
        "baseURL": "https://api.inference.boundless.network/v1",
        "apiKey": "{env:BOUNDLESS_API_KEY}"
      },
      "models": {
        "glm-5.2": {
          "name": "GLM-5.2",
          "limit": { "context": 262144, "output": 32000 },
          "reasoning": true,
          "tool_call": true
        },
        "dsv4": {
          "name": "DeepSeek V4 Flash",
          "limit": { "context": 262144, "output": 32000 },
          "reasoning": true,
          "tool_call": true
        },
        "qwen3.6": {
          "name": "Qwen3.6-35B-A3B",
          "limit": { "context": 262144, "output": 32000 },
          "reasoning": true,
          "tool_call": true,
          "modalities": { "input": ["text", "image"] }
        },
        "nemotron3-super": {
          "name": "Nemotron 3 Super",
          "limit": { "context": 262144, "output": 32000 },
          "reasoning": true,
          "tool_call": true
        },
        "kimi-k3": {
          "name": "Kimi K3",
          "limit": { "context": 1000000, "output": 32000 },
          "reasoning": true,
          "tool_call": true,
          "modalities": { "input": ["text", "image"] }
        },
        "qwen3-coder-next": {
          "name": "Qwen3 Coder Next",
          "limit": { "context": 262144, "output": 32000 },
          "reasoning": false,
          "tool_call": true
        },
        "qwen3.8-flash": {
          "name": "Qwen3.8 Flash",
          "limit": { "context": 1000000, "output": 32000 },
          "reasoning": true,
          "tool_call": true,
          "modalities": { "input": ["text", "image"] }
        },
        "glm-5.3-flash": {
          "name": "GLM-5.3 Flash",
          "limit": { "context": 1048576, "output": 32000 },
          "reasoning": true,
          "tool_call": true,
          "modalities": { "input": ["text", "image", "video"] }
        },
        "gpt-5.6-luna": {
          "name": "GPT-5.6 Luna",
          "limit": { "context": 262144, "output": 32000 },
          "reasoning": true,
          "tool_call": true
        },
        "gpt-5.6-sol": {
          "name": "GPT-5.6 Sol",
          "limit": { "context": 262144, "output": 32000 },
          "reasoning": true,
          "tool_call": true
        },
        "claude-opus-5": {
          "name": "Claude Opus 5",
          "limit": { "context": 262144, "output": 32000 },
          "reasoning": true,
          "tool_call": true
        },
        "claude-fable-5.1": {
          "name": "Claude Fable 5.1",
          "limit": { "context": 262144, "output": 32000 },
          "reasoning": true,
          "tool_call": true
        },
        "deepseek-v4-pro-0813": {
          "name": "DeepSeek V4 Pro",
          "limit": { "context": 1048576, "output": 32000 },
          "reasoning": true,
          "tool_call": true
        },
        "kimi-k2.5": {
          "name": "Kimi K2.5",
          "limit": { "context": 262144, "output": 32000 },
          "reasoning": true,
          "tool_call": true,
          "modalities": { "input": ["text", "image"] }
        },
        "mimo-v2.5": {
          "name": "MiMo-V2.5",
          "limit": { "context": 1000000, "output": 32000 },
          "reasoning": true,
          "tool_call": true,
          "modalities": { "input": ["text", "image", "audio", "video"] }
        },
        "minimax-m2.7": {
          "name": "MiniMax M2.7",
          "limit": { "context": 204800, "output": 32000 },
          "reasoning": true,
          "tool_call": true
        },
        "minimax-m3": {
          "name": "MiniMax M3",
          "limit": { "context": 1048576, "output": 32000 },
          "reasoning": true,
          "tool_call": true,
          "modalities": { "input": ["text", "image", "video"] }
        },
        "mimo-v2.5-pro": {
          "name": "MiMo-V2.5-Pro",
          "limit": { "context": 1048576, "output": 32000 },
          "reasoning": true,
          "tool_call": true
        },
        "kimi-k2.6": {
          "name": "Kimi K2.6",
          "limit": { "context": 262144, "output": 32000 },
          "reasoning": true,
          "tool_call": true,
          "modalities": { "input": ["text", "image"] }
        },
        "glm-5.1": {
          "name": "GLM-5.1",
          "limit": { "context": 202752, "output": 32000 },
          "reasoning": true,
          "tool_call": true
        },
        "hy3": {
          "name": "Hy3",
          "limit": { "context": 262144, "output": 32000 },
          "reasoning": true,
          "tool_call": true
        },
        "hy4-preview": {
          "name": "Hy4 preview",
          "limit": { "context": 1000000, "output": 32000 },
          "reasoning": true,
          "tool_call": true
        }
      }
    }
  }
}
```

A custom provider inherits nothing from OpenCode's own catalog, so each model has to state its own `limit`, and a model missing one shows a context window of 0 in the picker and gets truncated early. `reasoning` and `tool_call` are declared for the same reason — undeclared, they read as false and the picker says the model cannot do either.

`--auto` approves the local shell action it asks for, and older builds do not have it — run `opencode run --help` if it is rejected, and drop it to answer the permission prompt yourself:

```bash
opencode run --auto --model boundless/qwen3.6 'Use the bash tool to run printf boundless, then report the output.'
```

### Codex CLI

Add this to `~/.codex/config.toml`. Codex requires custom providers to use the Responses wire API, so keep `wire_api = "responses"`; Boundless translates that request to the selected open-weight model:

```toml
model = "qwen3.6"
model_provider = "boundless"
model_reasoning_effort = "low"
model_reasoning_summary = "none"

[model_providers.boundless]
name = "Boundless"
base_url = "https://api.inference.boundless.network/v1"
env_key = "BOUNDLESS_API_KEY"
wire_api = "responses"
```

Then export the key and start Codex in any repository:

```bash
export BOUNDLESS_API_KEY="YOUR_API_KEY"
codex
```

`qwen3.6` is the recommended Codex model because its streamed function calls are reliable on this surface. Codex performs conversation compaction locally for a custom provider, so the provider does not need a separate `/v1/responses/compact` endpoint.

### Cursor

Settings → Models. In the `API Keys` section, fill in **Override OpenAI Base URL**, put your key in the field labelled **OpenAI API Key** — it is sent to whatever base URL you set, despite the name — and add the model under **Custom models**:

```yaml
override_openai_base_url: https://api.inference.boundless.network/v1
openai_api_key: your-boundless-key
custom_model: glm-5.2
```

Two things stay on Cursor's own backend no matter what you set here. **Tab completion** is served by a Cursor model and is unaffected. **Composer and the agent models** refuse a custom key outright — selecting one returns `This model does not support custom API keys`, so pick your custom model explicitly rather than leaving the picker on Auto.

### GitHub Copilot

Copilot Chat reaches a custom endpoint through its **Custom Endpoint** BYOK provider, which speaks chat completions. Run **Chat: Manage Language Models** from the command palette, choose Custom Endpoint, and give it the base URL and your key; VS Code stores the key in its secret storage and writes only a reference into `chatLanguageModels.json`, so no key is ever in the file.

For the CLI, the same three values are environment variables:

```bash
export COPILOT_PROVIDER_BASE_URL="https://api.inference.boundless.network/v1"
export COPILOT_PROVIDER_API_KEY="your-boundless-key"
export COPILOT_MODEL="glm-5.2"
```

- **BYOK covers chat, not completions.** Inline suggestions keep using Copilot's own models and your subscription; only chat, agent and custom-agent turns reach this gateway.
- **On Business or Enterprise, an admin has to enable the policy first.** Without "Bring Your Own Language Model Key in VS Code" turned on, the Custom Endpoint option does not appear.
- **A failed model list means the base URL is wrong.** The provider probes `GET /models` to fill its picker; the URL has to end at `/v1`.

### OpenHands

The model name carries its own routing prefix: `openai/` is what tells OpenHands to treat the endpoint as OpenAI-compatible rather than looking for a provider it knows. In `config.toml`:

```toml
[llm]
model = "openai/glm-5.2"
base_url = "https://api.inference.boundless.network/v1"
api_key = "your-boundless-key"
```

The same three values live under Settings → LLM in the web UI, behind the **Advanced** toggle: `Custom Model`, `Base URL` and `API Key`. Leave off the `openai/` prefix and the run fails before it reaches us, naming a provider it could not resolve.

### Cline

Choose **OpenAI Compatible** as the API provider, then fill in the three fields it asks for:

```yaml
base_url: https://api.inference.boundless.network/v1
api_key: your-boundless-key
model_id: glm-5.2
```

Cline's **Model Configuration** panel below those fields sets the context window and max output tokens it assumes. Its defaults are conservative; the [model catalog](/docs/pricing) has the real context length for whichever model you picked.

### Mastra

An OpenAI-compatible provider from the AI SDK, handed to an agent as its model:

```typescript
import { Agent } from "@mastra/core/agent";
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";

const boundless = createOpenAICompatible({
  name: "boundless",
  baseURL: "https://api.inference.boundless.network/v1",
  apiKey: process.env.BOUNDLESS_API_KEY,
});

export const assistant = new Agent({
  name: "assistant",
  instructions: "You are a helpful coding assistant.",
  model: boundless("glm-5.2"),
});
```

`createOpenAICompatible` returns the provider; calling it with a model ID returns the model. Keep `@ai-sdk/openai-compatible` on the same AI SDK major version as `@mastra/core`, or the model type will not satisfy the agent's `model` field.

### Everything else

Roo Code, Continue, Aider, Zed and most other OpenAI-compatible clients all take the same three values. Choose the **OpenAI-compatible** provider, then fill in:

```yaml
base_url: https://api.inference.boundless.network/v1
api_key: your-boundless-key
model: glm-5.2
```

Type the model by hand if the tool's picker only lists its own. We serve `GET /v1/models`, so a tool that fetches the list populates its own picker instead.

**Two agents cannot be pointed here at all.** [Devin](https://docs.devin.ai) chooses its own models and exposes no way to supply an endpoint or a key. [Windsurf](https://docs.windsurf.com/plugins/cascade/models) offers bring-your-own-key only for models on its own list, with no editable base URL. Neither is a configuration problem to work around — the setting does not exist.

## When something doesn't work

| What you see | What it usually is |
| --- | --- |
| `401` on every request | The key is in the header the service doesn't read. Switch between the `Authorization: Bearer` and `x-api-key` settings your harness offers. |
| An error naming a model you never chose | The harness is still sending its own default. Set the model explicitly, including the small/background slot. |
| An empty reply, `stop_reason: max_tokens` | The output budget has to cover a reasoning model's thinking as well as its answer. Give it 1024 or more. |
| `429` with type `budget_exceeded` | Credits or the [key's budget](/docs/billing#key-budgets) reached zero. |
| `429` with a rate-limit message | You are at your team's [throughput cap](/docs/rate-limits). |

If it is none of those, [send us the model ID and a timestamp](/support) and we will look at the request.
