For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.
Bring your own agent
Run a container image of your own as a kagent agent by exposing the A2A service that the byo harness expects.
The byo harness runs a container image that you build, and treats what is inside it as opaque. kagent still builds a revisionRevisionThe compiled, immutable output of one Agent, identified by a content digest. A Session runs the revision it was created from for its whole life, so editing the Agent affects only sessions created afterward. from the HarnessHarnessA Kubernetes custom resource defining how an agent is allowed to run: which harness executes the agent, its workload image, and its WorkerPool and snapshot storage. An Agent pairs it with the AgentTemplate that it runs.Learn more and the AgentTemplateAgentTemplateA Kubernetes custom resource defining what an agent does: its model, system prompt, tools, skills, and plugins. It runs only once an Agent pairs it with a Harness.Learn more, schedules that revision onto Agent SubstrateAgent SubstrateThe runtime that kagent runs agents on. It multiplexes many sandboxed Actors onto a smaller pool of pre-started Workers, suspending idle ones to snapshots.Learn more, and routes conversations to it. What the image does with a message is yours to decide. Choose this harness when you already have an agent that is built with an agent framework, and you want to run that agent in kagent.
Responsibilities on each side of the A2A boundary
A2AA2AThe Agent-to-Agent protocol, which callers and other agents use to talk to an Agent. The conversation's context identifier is the Session ID, so a second message on the same ID continues the same conversation.Learn more (Agent-to-Agent) is the contract between kagent and a BYO agent.
Your image owns one side of the A2A contract by:
- Exposing an A2A service that accepts a message and returns a reply.
- Exposing a readiness endpoint that reports when the service can take traffic.
- Deciding what the agent actually does, including model calls, tool calls, and conversation state.
kagent runs a BYO agent exactly as it runs one on a managed harness, and owns the other side of the A2A contract by:
- Building an immutable revision from the HarnessHarnessA Kubernetes custom resource defining how an agent is allowed to run: which harness executes the agent, its workload image, and its WorkerPool and snapshot storage. An Agent pairs it with the AgentTemplate that it runs.Learn more and the AgentTemplate, and creating a SessionSessionA running conversation with one Agent. Unlike the resources it is built from, a Session is not a Kubernetes resource: kagent's gRPC API creates it and its PostgreSQL database tracks it.Learn more from it.
- Running the ActorActorThe sandboxed unit of compute, provided by Agent Substrate, that runs a Session's conversation loop. Every Session is backed by one.Learn more that hosts your image inside a sandbox. The WorkerPoolWorkerPoolA Kubernetes custom resource declaring how many Workers to keep running and which sandbox class they use. An operator must provision one before any Agent can compile.Learn more that the Harness references sets the class:
gvisor, which every installation provides, ormicrovm, which needs its own Agent Substrate provisioning. For more information, see Sandbox classes. - Suspending and resuming the Actor between turns, including its in-memory state.
- Routing every conversation through the A2A gateway, so callers address the AgentAgentA Kubernetes custom resource that pairs one AgentTemplate with one Harness. Each side takes either an inline spec or a reference to an existing resource, and the controller compiles the pair into a revision.Learn more rather than the Actor behind it.
- Delivering the agent configuration and agent card to the container, which the kagent helpers write to
/config/config.jsonand/config/agent-card.jsonat startup.
Because Agent Substrate snapshots the whole Actor, an image that keeps conversation state in memory keeps it across a suspend. Your image does not need to persist anything to survive the gap between turns.
The A2A contract
Both the Harness and the image must meet the following requirements, and kagent enforces them at different moments. Nothing checks the A2A service until a caller invokes the agent, so a misconfigured image reaches a healthy-looking state first. For examples of how to satisfy each requirement, see the steps in Configure a BYO Harness and Build the image.
| Requirement | Checked | Detail |
|---|---|---|
spec.workload.command is set on the Harness | When you apply the Harness | You must set the entrypoint override on a byo Harness. A manifest that omits it is rejected with BYO harnesses must specify workload.command. |
spec.workload.image is pinned by digest | When you apply the Harness | A byo Harness must name an image, and every Harness must pin it by sha256 digest. A manifest that omits the image is rejected with BYO harnesses must specify workload.image, and a tag alone fails the field’s pattern. |
| The container serves gRPC on port 80 | When a caller invokes the agent | kagent builds the agent card with a single interface, http://127.0.0.1:80 bound to gRPC, and that address is not configurable. The service is lf.a2a.v1.A2AService. |
The container answers GET /readyz on port 8081 | When Agent Substrate starts the Actor | Agent Substrate probes this path to decide when the Actor is ready, with a 30-second timeout. The probe is on a different port from the A2A service on purpose, so serve it independently. |
| The A2A service speaks gRPC, not JSON-RPC | When a caller invokes the agent | The agent card fixes the protocol binding to gRPC. An image that serves A2A over only JSON-RPC is never reached, regardless of the port it listens on. |
Warning
An image that serves A2A on another port still reports READY. Agent Substrate probes readiness on port 8081. That probe succeeds whether or not the A2A service on port 80 works, so the mismatch stays hidden until the first invoke. The invoke then fails in the gateway with Connect: tunnel failed. That message reports only that the gateway could not open a connection to the agent. It does not name a port. To bind the right port, read the runtime.port field of the configuration that kagent delivers. An image that ignores the configuration must bind port 80 itself.
Compare BYO and declarative agents
kagent resolves the AgentTemplate in the same way for a BYO agent as for a declarative agent, then delivers the result to the container as a JSON document in an environment variable. Both harnesses resolve a ModelConfig through the same code, so the same model providers work and the same ones are rejected. For the provider matrix, see Model provider support.
The kagent harness reads that document and assembles the agent from it. A BYO image decides whether to read it:
- An opaque agent ignores the document. The AgentTemplate exists to give the agent an identity and a description, and the image decides everything else, including which model to call and which tools to offer. Every field on an opaque AgentTemplate is optional,
modelConfigincluded. - A configured agent reads the document. The image applies the AgentTemplate’s system prompt, ModelConfig, tool bindings, skills, and plugins itself. Editing the AgentTemplate changes what the agent does without a rebuild.
Two AgentTemplate features continue to apply regardless of this choice. First, a byo Harness rejects structured output, with Harness runtime "byo" does not support structured output. Second, the agent card that kagent advertises for a byo Agent leaves out the AgentTemplate’s skills and declares no extension, so a client cannot discover human-in-the-loop support the way it can for a declarative agent.
Both kinds of BYO agent receive the same environment variables. Agent Substrate delivers the two JSON payloads as private variables, and the kagent helpers write them to files before the agent starts. An image in another language either reads the files that MaterializeFromEnv writes or parses the variables itself.
| Environment variable | Contents |
|---|---|
KAGENT_CONFIG_JSON, which the helpers write to /config/config.json | The agent configuration, including the resolved system prompt, the model and its settings, every tool, skill, and plugin that the AgentTemplate binds, and a runtime object that sets the process settings. |
KAGENT_AGENT_CARD_JSON, which the helpers write to /config/agent-card.json | The agent card that kagent advertises for this agent, rendered as JSON. |
Anything in spec.env | The literal values that the Harness sets. |
Certificate-authority variables and SSL_CERT_DIR | The egress gateway’s trust bundle, which every Actor receives. For the list and the limit on how many variables an Actor takes, see Environment variables on an Actor. |
The runtime object is the root of the configuration document, and subagents inherit it. The object includes the settings that a process needs before it serves a request.
{
"runtime": {
"control_plane_url": "http://kagent-controller.kagent.svc:8083",
"gateway_url": "http://kagent-controller.kagent.svc:8083",
"name": "my-byo-agent",
"namespace": "kagent",
"log_level": "info",
"port": "80",
"max_content_length": 0
}
}| Field | Contents |
|---|---|
control_plane_url | The kagent API that the agent reports task state to. Required by the Go and Python helpers. |
gateway_url | The A2A gateway that the agent reaches other agents and MCP servers through. |
name and namespace | The Agent’s name and the AgentTemplate’s namespace, which kagent’s helpers use to scope sessions. |
port | The port to serve A2A on. The controller sets 80 for every harness, and the agent card advertises that same port. |
log_level | debug, info, warn, or error, from the config.runtimeDefaults.logging.level Helm value. |
max_content_length | The largest A2A request body to accept, in bytes. Zero means unlimited, and an absent field keeps the helper’s own default. |
Configure a BYO Harness
A byo Harness takes the same fields as any other Harness. Its own block, spec.byo, is an empty object, because the image defines the behavior that the other harnesses take settings for.
kubectl apply -f - <<EOF
apiVersion: api.kagent.dev/v1alpha3
kind: Harness
metadata:
name: my-byo-harness
namespace: kagent
spec:
byo: {}
workload:
image: <your-registry>/my-agent@sha256:<digest>
command: ["/my-agent"]
substrate:
workerPoolRef:
name: kagent-default
snapshotPolicy:
location: s3://ate-snapshots/kagent/
EOFReview the following table to understand this configuration. For the fields that every Harness shares, see Configure a Harness.
| Field | Required | Description |
|---|---|---|
byo | Yes | Selects this harness. The object is always empty, and naming a second harness alongside it is rejected. |
workload.image | Yes | Your image, pinned by sha256 digest. A tag alone is rejected, because a revision must be reproducible. |
workload.command | Yes | The entrypoint to run, up to 32 entries. Required for byo and optional for every other harness. |
env | No | Any setting that your image reads. Each entry takes a literal value only. Put credentials in a ModelConfig or a RemoteMCPServer instead. |
An opaque agent’s AgentTemplate sets only a description for the agent card. An Agent then pairs it with the Harness.
kubectl apply -f - <<EOF
apiVersion: api.kagent.dev/v1alpha3
kind: AgentTemplate
metadata:
name: my-byo-template
namespace: kagent
spec:
description: An agent that my own image implements.
---
apiVersion: api.kagent.dev/v1alpha3
kind: Agent
metadata:
name: my-byo-agent
namespace: kagent
spec:
template:
ref:
name: my-byo-template
harnessRef:
name: my-byo-harness
EOFTo configure the agent from Kubernetes instead, add the fields that any AgentTemplate takes, and read /config/config.json in the image. For what those fields mean, see Your first agent.
Build the image
The A2A contract is a gRPC service and a readiness endpoint, so any language with a gRPC server can satisfy it. kagent ships helpers for Go and Python. To build and run a minimal image end to end before writing your own, see Run your own agent image.
Tip
The Go helper produces smaller images, faster startup, and lower memory use than the Python one. Where both suit the agent that you are building, prefer Go.
github.com/kagent-dev/kagent/go/adk/pkg/app serves the A2A gRPC service, the readiness endpoint on 8081, and the agent card, given any type that implements a2asrv.AgentExecutor. The helper is framework-agnostic, so the executor is the only part that you write. config.MaterializeFromEnv writes the payloads that Agent Substrate delivers to /config, and config.LoadAgentConfig reads the document back.
import (
"cmp"
"context"
"fmt"
"iter"
"os"
a2atype "github.com/a2aproject/a2a-go/v2/a2a"
"github.com/a2aproject/a2a-go/v2/a2asrv"
"github.com/kagent-dev/kagent/go/adk/pkg/app"
"github.com/kagent-dev/kagent/go/adk/pkg/config"
"github.com/kagent-dev/kagent/go/pkg/logging"
)
if err := config.MaterializeFromEnv("/config"); err != nil {
return err
}
compiled, err := config.LoadAgentConfig("/config/config.json")
if err != nil {
return err
}
runtime := compiled.ProcessConfig()
application, err := app.New(app.AppConfig{
AgentCard: a2atype.AgentCard{
Name: "my-agent",
Version: "v1",
Capabilities: a2atype.AgentCapabilities{Streaming: true},
},
ControlPlaneURL: runtime.ControlPlaneURL,
MaxContentLength: runtime.MaxContentLength,
Port: cmp.Or(runtime.Port, "80"),
AppName: cmp.Or(runtime.Name, "my-agent"),
Logger: logger,
}, myExecutor{})
if err != nil {
return err
}
return application.Run()app.New takes an application configuration and an executor, and returns an application that serves the A2A gRPC service and the agent card on the port that the configuration names. These settings in app.AppConfig decide whether kagent can reach the result:
Portis the port the A2A listener binds.app.AppConfigdefaults it to8080, which kagent never dials. Passcmp.Or(runtime.Port, "80")instead to take the port from the configuration and fall back to80when the configuration omits it.ControlPlaneURL, or aControllerClientof your own, is required because the helper reports task state to the kagent API.AppNamescopes sessions. Theruntime.Namevalue is the Agent’s name.
For a complete executor, see go/core/test/byoa2a/main.go in the kagent repository.
Known limitations
- The A2A interface is fixed. kagent advertises
http://127.0.0.1:80over gRPC, andspec.byodoes not take a field that changes the address, the port, or the protocol. An image that serves A2A over only HTTP JSON-RPC cannot run on this harness. - The LangGraph, CrewAI, and OpenAI adapters do not satisfy the contract. The
kagent-langgraph,kagent-crewai, andkagent-openaipackages each build a FastAPI application with A2A JSON-RPC routes, without a gRPC server. Running LangGraph, CrewAI, or the OpenAI Agents SDK underbyomeans serving A2A over gRPC yourself, as the Any other language tab in Build the image describes. - Long-term memory is unavailable. Memory is configured under
spec.kagent.memory, and only thekagentharness wires it up. AbyoHarness does not take an equivalent setting. For what a BYO image would need to replace, see Agent memory. - A broken port mapping looks like a healthy agent. For the failure and what avoids it, see The A2A contract.