The kagent Harness: Declarative Agents, Memory and Compaction

Part of: Agent Runtimes

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.

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 loopkagent’s Go engineYour imageThe coding agent
Change behavior byEditing the AgentTemplateRebuilding the image (unless it reads the config)Editing the AgentTemplate, within limits
Structured output✅❌❌
Long-term memory✅❌—
UI file attachments✅❌❌
Model providersAll 8 supportedAll 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:

StrategyFieldsWhat it does
Tail retentiontokenThreshold + eventRetentionSizePast N tokens, summarize older history. Keep the latest events verbatim
Sliding windowcompactionInterval (+ 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 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 ).

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.

Frequently asked questions

What is the kagent harness?

It’s kagent’s built-in Go engine. A Harness with spec.kagent set runs an agentic loop defined entirely by an AgentTemplate (system prompt, model, tools, skills, plugins). To change what the agent does, you edit the template, not an image.

What does the kagent harness support that Claude Code, Codex and BYO don't?

Structured output, long-term memory, UI file attachments, partial MCP tool selection, MCP tokenPassthrough, and every supported model provider (OpenAI, Anthropic, Bedrock, AzureOpenAI, Gemini, Mistral, Ollama, Foundry). It also adds agent, conversation, task and tool-call identity to W3C baggage when baggage propagation is on.

How does kagent context compaction work?

Two strategies, configured in spec.kagent.compaction on the Harness. Tail retention summarizes older history once the prompt passes tokenThreshold, keeping the last eventRetentionSize events in full. Sliding window summarizes every compactionInterval invocations, with overlapSize for continuity. You can set both. By default the summarizer is the agent’s own model.

How long does kagent agent memory last?

Set memory.ttlDays on the Harness (minimum 1). If omitted, the server default is 15 days. Memory needs an embedding model through memory.modelConfigRef.

Disagree or have a war story? Reply on X