# kagent Codex Harness: Run OpenAI Codex on Kubernetes

> How to run OpenAI Codex as a kagent 1.0 harness: the four resources, the ModelConfig rules, MCP and env restrictions, and how to debug a Compatible=False agent.

- Canonical URL: https://webofmike.com/kagent-codex-harness/
- Author: Mike Moore (https://webofmike.com/about/)
- Published: 2026-11-03
- Last modified: 2026-10-30
- Tags: AI Agents, Kubernetes, Platform Engineering
- Cite as: Mike Moore, "kagent Codex Harness: Run OpenAI Codex on Kubernetes", Web of Mike (webofmike.com), 2026-11-03. https://webofmike.com/kagent-codex-harness/


My [Claude Code harness post](/kagent-v1-agentinstance-claude-code-harness/) gets the questions. The follow-up is usually "can I do the same thing with Codex?" Yes. Here's how, and where it bites.

*Disclosure: I work at Solo.io, which created kagent (now an open-source project) and sells Solo Enterprise for kagent. This post is built from the public [Solo Enterprise for kagent harness docs](https://docs.solo.io/kagent/1.0.x/agents/agent-harness/) as of 2026-10-30. kagent 1.0 is alpha, so field names can still move.*

## TL;DR

- `spec.codex: {}` on a Harness runs Codex as the agent loop, on Agent Substrate.
- The ModelConfig must use `openAI.apiFormat: responses`, and the key must be entitled to the model.
- MCP servers must be Streamable HTTP. No structured output, no token passthrough.

## Why run Codex in the cluster

Codex on a laptop is one process that holds your key, your shell and your session. On kagent, the session runs in a sandboxed Actor that can suspend to object storage and resume, the model key sits behind the egress gateway instead of in the agent's environment, and every edit to the agent produces a new digest-pinned revision. Sessions already running keep the revision they started with. More on that split in [what a cloud-native agent harness is](/what-is-a-cloud-native-agent-harness/).

## The four resources

ModelConfig (model + key), Harness (runtime + infra), AgentTemplate (behavior), Agent (the pairing). This is straight from the docs. It assumes the `kagent-openai` Secret from the OpenAI setup guide and a `kagent-default` WorkerPool.

```yaml
apiVersion: api.kagent.dev/v1alpha3
kind: ModelConfig
metadata:
  name: codex-model-config
  namespace: kagent
spec:
  apiKeySecret: kagent-openai
  apiKeySecretKey: OPENAI_API_KEY
  model: gpt-5.2-codex
  provider: OpenAI
  openAI:
    apiFormat: responses
---
apiVersion: api.kagent.dev/v1alpha3
kind: Harness
metadata:
  name: codex-harness
  namespace: kagent
spec:
  codex: {}
  substrate:
    workerPoolRef:
      name: kagent-default
    snapshotPolicy:
      location: s3://ate-snapshots/kagent/
---
apiVersion: api.kagent.dev/v1alpha3
kind: AgentTemplate
metadata:
  name: codex-template
  namespace: kagent
spec:
  modelConfig:
    name: codex-model-config
  systemPrompt: You are a concise, helpful coding assistant.
---
apiVersion: api.kagent.dev/v1alpha3
kind: Agent
metadata:
  name: codex-agent
  namespace: kagent
spec:
  template:
    ref:
      name: codex-template
  harnessRef:
    name: codex-harness
```

I left `workload.image` off the Harness on purpose. Omit it and you get the digest-pinned Codex image that ships with your kagent release. Override it per Harness, or install-wide with `config.harnessImages.codex` in Helm. Either way it must be pinned by `sha256`.

## Gotchas

| Gotcha | What happens |
|---|---|
| ModelConfig not `apiFormat: responses` | Rejected. Only `baseUrl` is allowed alongside it |
| Key not entitled to the model | Turn fails with `Codex execution failed` |
| Bedrock | Only OpenAI `gpt-*` model IDs, only `region` setting, and an `AWS_BEARER_TOKEN_BEDROCK` key (IAM keys rejected) |
| SSE MCP server | Rejected. Use `STREAMABLE_HTTP` |
| `tokenPassthrough`, `defaultHeaders`, `tls` | Rejected |
| Structured output | Rejected |
| Setting `CODEX_HOME`, `OPENAI_API_KEY` or `AWS_*` creds in `spec.env` | Every Agent on that Harness goes `Compatible=False` |
| Subagents | Can't carry their own tools or skills, and must share provider + creds |
| File attachments in UI chat | kagent harness only |

The env one catches people who try to "just pass the key." Don't. Keys belong in the ModelConfig Secret, and the gateway injects them.

## Check it

```bash
kubectl describe agent codex-agent -n kagent
kubectl get agent codex-agent -n kagent -o jsonpath='{.status.workloadImage}'
```

`ResolvedRefs` false usually means a missing WorkerPool. `Compatible` false means one of the gotchas above. `Ready` false means Substrate capacity isn't up yet. The Harness itself never reports status, so stop staring at `kubectl get harness`.

## Telemetry

With tracing on and `captureContent: true`, Codex prompts and replies land on the `invoke_agent` span, and tool arguments and results land on `execute_tool`. `maxCaptureBytes` (default 16384, max 65536) caps each one. Default is off, and that's right: anyone who can read your traces can read what you capture. To flip it for just this Harness, set `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` to `SPAN_ONLY` or `NO_CONTENT` in `spec.env`.

## Codex vs Claude harness

| | codex | claude |
|---|---|---|
| Providers | OpenAI (responses), Bedrock gpt-* | Anthropic, Bedrock |
| MCP | Streamable HTTP only | Binds whole server, ignores subsets |
| Raw API body capture | No | Yes (`captureRawApiBodies`) |
| Structured output | No | No |

All four side by side: [kagent harnesses compared](/kagent-harnesses-compared/).

