Disclosure: this issue was written by an AI agent (Claude) while verifying what a
bring-your-own-agent guide must tell readers, and reviewed by a human before filing.
Summary
The byo harness compiler injects no PORT, but the agent card it builds advertises the
agent at http://127.0.0.1:80. kagent's own BYO helper (go/adk/pkg/app) defaults to
8080. So a BYO image built the documented way reports READY and then fails every
invoke, because the gateway dials a port nothing is listening on.
The instance being READY is what makes this bad: readiness is probed on a different
port that succeeds regardless, so nothing surfaces the mismatch.
Environment
kagent b8d53d39 (chart kagent-v0.10.0-rc1-129-gb8d53d39), substrate 0.0.25, kind, Kubernetes 1.35.
Reproduction
Built a BYO image from go/core/test/byoa2a/main.go with the one line Port: "80" removed,
so app.New falls back to PORT and then to its 8080 default:
application, err := app.New(app.AppConfig{
AgentCard: a2atype.AgentCard{Name: "byoport", Version: "v1", ...},
AppName: "byoport", Logger: logger, // no Port set
}, executor{})
Harness and AgentTemplate:
apiVersion: kagent.dev/v1alpha3
kind: Harness
metadata: {name: byoport, namespace: kagent}
spec:
byo: {}
allowedAgentTemplates: {selector: {matchLabels: {kagent.dev/harness: byoport}}}
workload:
image: localhost:5001/byoport@sha256:ea596db3dac8da570980143210efeb2b47bcfb0a3afc5aa0f325a6063c5cf009
command: ["/byoport"]
substrate:
workerPoolRef: {name: kagent-default}
snapshotPolicy: {location: s3://ate-snapshots/kagent}
Result — the template goes fully ready:
Accepted=True(Accepted) ResolvedRefs=True(Resolved) Compatible=True(Compatible) Ready=True(Ready)
The AgentInstance is created READY, and the invoke fails:
$ kagent create agent-instance --agent-template byoport-agent --harness byoport
| 01a08301-8cd0-72c8-818f-26c7490ce37d | byoport-agent | byoport | READY | ... |
$ kagent invoke --agent-instance 01a08301-8cd0-72c8-818f-26c7490ce37d --task "hello"
Error: invoke AgentInstance: upstream call failed: Connect: tunnel failed
Adding PORT to the Harness fixes it with no image change:
kubectl patch harness byoport -n kagent --type=merge \
-p '{"spec":{"env":[{"name":"PORT","value":"80"}]}}'
$ kagent invoke --agent-instance <new id> --task "hello"
BYO agent response
That is the proof: same image, same command, only PORT differs.
Why it happens
go/core/internal/translator/kagent/compiler.go:73 injects corev1.EnvVar{Name: "PORT", Value: "80"}.
go/core/internal/translator/byo/compiler.go injects no PORT. Its environment is
only compiled.Environment plus adkconfig.HarnessEnvironment(harness), i.e. the
Harness's own spec.env.
go/core/internal/translator/byo/compiler.go:64 nonetheless builds the card with
SupportedInterfaces: [{URL: "http://127.0.0.1:80", ...}].
go/adk/pkg/app/app.go:23,161-165 — cfg.Port -> PORT env -> defaultPort = "8080".
The e2e image passes only because go/core/test/byoa2a/main.go:40 hardcodes Port: "80".
The other in-repo reference, go/adk/examples/byo/main.go, documents PORT as
"default 8080" and would not work unpinned.
Why readiness does not catch it
go/core/internal/substrate/actor_template.go:69-71 probes /readyz on port 8081, and
go/adk/pkg/a2a/server/server.go:134 serves that on :8081 independently of PORT. So the
health port is satisfied while the A2A port is wrong.
Expected
One of:
byo/compiler.go injects PORT=80 the way the kagent compiler does, so the card and the
runtime agree by construction; or
- the card is built from the port the image actually uses, with a documented field for it; or
- the mismatch is rejected or surfaced as a condition instead of presenting as
READY.
Option 1 matches existing behaviour for the other harnesses and needs no API change.
Adjacent, not tested
byo/compiler.go:64 sets ProtocolBinding: TransportProtocolGRPC while app.New
advertises a2atype.NewAgentInterface("/", TransportProtocolJSONRPC) (app.go:180).
Whether the gateway cares was not tested here.
Summary
The
byoharness compiler injects noPORT, but the agent card it builds advertises theagent at
http://127.0.0.1:80. kagent's own BYO helper (go/adk/pkg/app) defaults to8080. So a BYO image built the documented way reports
READYand then fails everyinvoke, because the gateway dials a port nothing is listening on.
The instance being
READYis what makes this bad: readiness is probed on a differentport that succeeds regardless, so nothing surfaces the mismatch.
Environment
kagent
b8d53d39(chartkagent-v0.10.0-rc1-129-gb8d53d39), substrate 0.0.25, kind, Kubernetes 1.35.Reproduction
Built a BYO image from
go/core/test/byoa2a/main.gowith the one linePort: "80"removed,so
app.Newfalls back toPORTand then to its 8080 default:Harness and AgentTemplate:
Result — the template goes fully ready:
The AgentInstance is created
READY, and the invoke fails:Adding
PORTto the Harness fixes it with no image change:kubectl patch harness byoport -n kagent --type=merge \ -p '{"spec":{"env":[{"name":"PORT","value":"80"}]}}'That is the proof: same image, same command, only
PORTdiffers.Why it happens
go/core/internal/translator/kagent/compiler.go:73injectscorev1.EnvVar{Name: "PORT", Value: "80"}.go/core/internal/translator/byo/compiler.goinjects noPORT. Its environment isonly
compiled.Environmentplusadkconfig.HarnessEnvironment(harness), i.e. theHarness's own
spec.env.go/core/internal/translator/byo/compiler.go:64nonetheless builds the card withSupportedInterfaces: [{URL: "http://127.0.0.1:80", ...}].go/adk/pkg/app/app.go:23,161-165—cfg.Port->PORTenv ->defaultPort = "8080".The e2e image passes only because
go/core/test/byoa2a/main.go:40hardcodesPort: "80".The other in-repo reference,
go/adk/examples/byo/main.go, documentsPORTas"default 8080" and would not work unpinned.
Why readiness does not catch it
go/core/internal/substrate/actor_template.go:69-71probes/readyzon port 8081, andgo/adk/pkg/a2a/server/server.go:134serves that on:8081independently ofPORT. So thehealth port is satisfied while the A2A port is wrong.
Expected
One of:
byo/compiler.goinjectsPORT=80the way the kagent compiler does, so the card and theruntime agree by construction; or
READY.Option 1 matches existing behaviour for the other harnesses and needs no API change.
Adjacent, not tested
byo/compiler.go:64setsProtocolBinding: TransportProtocolGRPCwhileapp.Newadvertises
a2atype.NewAgentInterface("/", TransportProtocolJSONRPC)(app.go:180).Whether the gateway cares was not tested here.