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 implementing the A2A service that the byo runtime expects.
The byo runtime runs a container image that you build, and treats what is inside it as opaque. kagent still compiles the HarnessHarnessA Kubernetes custom resource defining how an agent is allowed to run: its runtime, workload image, and WorkerPool and snapshot storage. An Agent pairs it with the AgentTemplate it runs.Learn more and 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 into 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., 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 runtime when you have an agent framework that kagent does not adapt, and you would rather bring the image than the integration.
What kagent runs and what your image runs
The byo runtime divides the work at the 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) boundary. Everything on kagent’s side of that boundary behaves in the same way as for the built-in runtimes, so a BYO agent is sandboxed, snapshotted, and addressed identically to one that kagent executes itself.
kagent owns the lifecycle, the isolation, and the routing:
- Compiles an 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 into an immutable revision, and creates 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.
- Wraps 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 the image runs in with a gVisor sandbox.
- Suspends and resumes the Actor between turns, including its in-memory state.
- Routes every conversation through the A2A gateway, so callers address the Agent rather than the Actor behind it.
- Delivers the compiled agent configuration and agent card to the container, which the kagent helpers write to
/config/config.jsonand/config/agent-card.jsonat startup.
Your image owns the agent’s behavior and the two endpoints that expose it:
- An A2A service that accepts a message and returns a reply.
- A readiness endpoint that reports when the service can take traffic.
- Whatever the agent actually does: model calls, tool calls, and conversation state.
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
A byo image must meet four requirements to run as an agent. kagent enforces only spec.workload.command at apply time, and the rest surface as a failed invoke rather than as a validation error. The steps in the Configure a byo Harness and Build the image sections later in this page show how to satisfy each requirement.
| Requirement | Detail |
|---|---|
spec.workload.command is set on the Harness | A byo Harness must override the image entrypoint, because kagent does not infer it. |
| The container serves gRPC on port 80 | 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 | 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 | The agent card fixes the protocol binding to gRPC. An image that serves A2A over JSON-RPC alone is never reached, whatever port it listens on. |
Warning
An image that listens on another port still reports READY. Readiness is probed on port 8081, which succeeds no matter what the A2A service does, so nothing surfaces the mismatch until an invoke fails with Connect: tunnel failed. The compiled configuration sets port 80 in its runtime.port field, so an image that reads the configuration binds the right port. An image that ignores the configuration must pin port 80 itself. This gap is tracked as kagent#2758.
Opaque and configured agents
kagent compiles an AgentTemplate for a byo Harness in the same way as for the kagent runtime, then hands the result to the container. The two ways of using this runtime differ only in whether the image reads it.
- An opaque agent ignores the compiled configuration. 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 compiled configuration. The image honors the AgentTemplate’s system prompt, ModelConfig, tool bindings, skills, and plugins, so an operator changes the agent’s behavior by editing the AgentTemplate rather than by rebuilding the image.
The kagent and byo runtimes compile through the same path, so a configured BYO agent accepts the same model providers and the same AgentTemplate features as the built-in runtime. For the provider matrix, see Model provider support. For what an AgentTemplate can bind, see Tool and skill support.
Both kinds of agent receive the same inputs. Agent Substrate delivers the two JSON payloads as private environment 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.
| Input | Contents |
|---|---|
/config/config.json | The compiled agent configuration: 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. |
/config/agent-card.json | The agent card that kagent advertises for this agent, rendered as JSON. |
Anything in spec.env | Literal values and credentialRef lookups from the Harness, resolved before the Actor starts. Agent Substrate accepts at most 32 environment variables on an Actor, and the compiled configuration counts toward that limit. A revision that exceeds it fails to compile rather than starting and misbehaving. |
The runtime object is the root of the configuration document, and subagents inherit it. It 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. The Go and Python helpers both require it. |
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 own helpers use to scope sessions. |
port | The port to serve A2A on. The controller sets 80 for every runtime, 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. |
Important
The process settings moved into the configuration document in 1.0.0-alpha13. An image that reads KAGENT_PORT, KAGENT_NAME, KAGENT_NAMESPACE, KAGENT_API_URL, or KAGENT_A2A_GRPC_ADDRESS finds none of them set, and naming one in spec.env sets a variable that nothing reads. Read the runtime object instead.
Configure a BYO Harness
A byo Harness takes the same fields as any other, minus the runtime settings block. spec.byo is an empty object, because the runtime has nothing to configure. The image holds the behavior.
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 runtime. The object is always empty, and naming a second runtime 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 runtime. |
env | No | Provider credentials and any setting that your image reads. Each entry takes a literal value or a credentialRef. |
An opaque agent’s AgentTemplate carries 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 two of them. 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.
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()ControlPlaneURL is required unless you supply a ControllerClient of your own, because the helper reports task state to the kagent API. Port defaults to 8080 when both the field and runtime.port are empty, and kagent never dials that port, so keep the cmp.Or fallback to 80.
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.byotakes no field to change the address, the port, or the protocol. An image that serves A2A over HTTP JSON-RPC alone cannot run on this runtime. - The
kagent-langgraphandkagent-crewaiadapters do not qualify yet. Both build a FastAPI application with A2A JSON-RPC routes and no gRPC server, so neither satisfies the contract as shipped. Running LangGraph or CrewAI underbyocurrently means serving A2A over gRPC yourself. - Long-term memory is unavailable. Memory is configured under
spec.kagent.memoryand wired only by thekagentruntime’s compiler. AbyoHarness has no equivalent setting. For what a BYO image would need to replace, see Agent memory. - A broken port mapping presents as a healthy agent. For the failure and what avoids it, see The A2A contract.