The Claude Code
and Codex
harnesses get the attention because people already know those loops. The harness I’d actually default to is the boring one: kagent itself. You describe the agent, kagent runs the loop, and you get the whole feature set.
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 declarative agents and harness docs as of 2026-10-30. kagent 1.0 is alpha.
Declarative vs everything else
A declarative agent is one you describe rather than build. The AgentTemplate holds the prompt, model, tools, skills and plugins. The kagent harness runs a Go engine that reads that config and runs the loop. Changing behavior means editing YAML, not rebuilding an image.
Declarative (kagent) | BYO (byo) | claude / codex | |
|---|---|---|---|
| Who owns the loop | kagent’s Go engine | Your image | The coding agent |
| Change behavior by | Editing the AgentTemplate | Rebuilding the image (unless it reads the config) | Editing the AgentTemplate, within limits |
| Structured output | ✅ | ❌ | ❌ |
| Long-term memory | ✅ | ❌ | — |
| UI file attachments | ✅ | ❌ | ❌ |
| Model providers | All 8 supported | All 8 (if the image reads config) | 2 each |
All of them run in the same sandbox, follow the same snapshot policy and go through the same gateway. The difference is who’s driving.
The engine settings: memory and compaction
spec.kagent on the Harness holds settings that belong to the engine, not the agent. That means every agent on the Harness gets them.
spec:
kagent:
memory:
modelConfigRef:
name: embedding-model-config
ttlDays: 30
compaction:
tokenThreshold: 120000
eventRetentionSize: 20
Memory persists facts across conversations. It needs an embedding ModelConfig in the Harness’s namespace. ttlDays defaults to 15 and has a minimum of 1.
Compaction keeps one conversation’s prompt under control. It has two strategies, and you can use both:
| Strategy | Fields | What it does |
|---|---|---|
| Tail retention | tokenThreshold + eventRetentionSize | Past N tokens, summarize older history. Keep the latest events verbatim |
| Sliding window | compactionInterval (+ optional overlapSize) | Summarize every N completed invocations, overlapping for continuity |
compaction.summarizer lets you put a cheaper model on summaries. Otherwise it uses the agent’s own model. My take: set compaction on any agent that runs long sessions. Unbounded context is a cost problem first and a quality problem second.
A complete declarative agent
Straight from the docs. It assumes the kagent-openai Secret and a kagent-default WorkerPool.
apiVersion: api.kagent.dev/v1alpha3
kind: ModelConfig
metadata:
name: kagent-model-config
namespace: kagent
spec:
apiKeySecret: kagent-openai
apiKeySecretKey: OPENAI_API_KEY
model: gpt-4o-mini
provider: OpenAI
openAI: {}
---
apiVersion: api.kagent.dev/v1alpha3
kind: Harness
metadata:
name: kagent-harness
namespace: kagent
spec:
kagent: {}
substrate:
workerPoolRef:
name: kagent-default
snapshotPolicy:
location: s3://ate-snapshots/kagent/
---
apiVersion: api.kagent.dev/v1alpha3
kind: AgentTemplate
metadata:
name: kagent-template
namespace: kagent
spec:
modelConfig:
name: kagent-model-config
systemPrompt: You are a concise, helpful assistant.
---
apiVersion: api.kagent.dev/v1alpha3
kind: Agent
metadata:
name: my-agent
namespace: kagent
spec:
template:
ref:
name: kagent-template
harnessRef:
name: kagent-harness
kagent agent get my-agent will show READY False right after apply. That’s expected, because kagent builds a snapshot first. If it stays False, kubectl describe agent and walk ResolvedRefs → Compatible → Ready.
Why it’s the security-friendly default
- Tool subsets actually hold. The Claude harness binds a whole MCP server even when you name a few tools. The kagent harness binds what you name.
- Provider choice. Gemini, Mistral, Ollama, Azure OpenAI and Foundry only work here or on BYO.
- Identity in traces. With
OTEL_PROPAGATORS=tracecontext,baggage, it addsgen_ai.agent.name,gen_ai.conversation.id,a2a.task.idand (on tool calls)gen_ai.tool.call.idto each model and MCP call. The claude and codex harnesses don’t. One caveat: turning baggage on also forwards whatever your callers attach to your model provider, so only enable it where you trust them. - No code to own. No wrapper, no restore surprises (ask me how I know ).
When not to use it
When you specifically want Claude Code’s or Codex’s own loop, or when the agent already exists in another framework. Then it’s claude, codex or byo . Otherwise, start here.