# The kagent Harness: Declarative Agents, Memory and Compaction

> kagent's built-in harness runs a Go engine that reads your AgentTemplate: why it's the default, how memory and compaction work, and what you get that Claude Code, Codex and BYO don't.

- Canonical URL: https://webofmike.com/kagent-native-harness-declarative-agents/
- Author: Mike Moore (https://webofmike.com/about/)
- Published: 2026-11-05
- Last modified: 2026-10-30
- Tags: AI Agents, Kubernetes, Platform Engineering
- Cite as: Mike Moore, "The kagent Harness: Declarative Agents, Memory and Compaction", Web of Mike (webofmike.com), 2026-11-05. https://webofmike.com/kagent-native-harness-declarative-agents/


The [Claude Code](/kagent-v1-agentinstance-claude-code-harness/) and [Codex](/kagent-codex-harness/) 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](https://docs.solo.io/kagent/1.0.x/agents/declarative-agents/) and [harness](https://docs.solo.io/kagent/1.0.x/agents/agent-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.

```yaml
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.

```yaml
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 adds `gen_ai.agent.name`, `gen_ai.conversation.id`, `a2a.task.id` and (on tool calls) `gen_ai.tool.call.id` to 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](/kagent-byo-harness-factory-droid/)).

## 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](/kagent-harnesses-compared/). Otherwise, start here.

