fix(controller): don't mount the declarative agent config on BYO agents - #2573
Open
rtemperini wants to merge 3 commits into
Open
fix(controller): don't mount the declarative agent config on BYO agents#2573rtemperini wants to merge 3 commits into
rtemperini wants to merge 3 commits into
Conversation
BYO agents run their own image and do not share the declarative runtime's configuration schema. Since b04769e the compiler builds a minimal AgentConfig for them, and buildConfigSecret rendered it into config.json and mounted it at /config because it only checked that the config was non-nil. That config carries no model. The runtime schema requires one, so a BYO image that loads /config/config.json on startup fails validation and crashloops. Nothing is logged, because rendering the config succeeds. Gate the rendered config and its volume on the agent type, restoring the behaviour BYO agents had before b04769e, where they received no config volume at all. The Secret keeps its config.json key, since the substrate backend injects it as a secret-backed env var, but leaves it empty; the runtime skips materializing empty values. The one case where a BYO agent still needs the volume is a sandbox config, which populates srt-settings.json. That path is unchanged and is covered by a test. Signed-off-by: Ricardo Temperini <29879569+rtemperini@users.noreply.github.com>
A nil Model marshalled to "model":null, which the python runtime rejects with a model_attributes_type validation error before it can read any other field. This is defence in depth, not a fix on its own. The runtime declares model as required with no default, so a config with no model is invalid input either way; omitting the key only changes the error from model_attributes_type to "Field required". Callers that must not receive an agent config have to be excluded upstream of marshalling. AgentConfig.UnmarshalJSON already treats an absent model the same as a null one, so the config still round-trips. Signed-off-by: Ricardo Temperini <29879569+rtemperini@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2571.
Targets
release/v0.10.xdeliberately, notmain— see Why this branch below.What's broken
Since
b04769e8(#2171) the compiler builds a minimalAgentConfigfor BYO agents.buildConfigSecretgated the rendered config and its volume on a bareif cfg != nilwith no agent-type distinction, so BYO agents started receiving
/config/config.jsoncontaining{"model":null,"description":"…","instruction":""}.The runtime schema requires a model (
model: ModelUnion = Field(discriminator="type"),required, no default), so a BYO image that loads that file on startup fails validation
and crashloops:
Rendering the config succeeds, so the controller logs nothing — the only symptom is
the agent pod crash looping, which points at the user's image rather than at the
rendered config.
First affected tag is
v0.10.0-beta7;v0.10.0-beta6is the last clean0.10.x. Nov0.9*tag containsb04769e8.The fix
1. Gate the rendered config and its volume on agent type (
manifest_builder.go).A new
needsAgentConfigpredicate sits next to the existingneedsSRTSettingscarve-out, matching how BYO is already special-cased in this file. This restores the
pre-
b04769e8behaviour where BYO agents received no config volume at all.I chose this over restoring
cfg = nilfor BYO in the compiler, because that is not aclean revert:
compiler.go:160dereferencescfg.SessionDBURLunguarded when theagent runs in sandbox workload mode, so a nil
cfgwould panic there. Gating inbuildConfigSecretalso leaves the declarative path byte-identical — the only goldenfixture that changes is the BYO one.
Two details worth flagging for review:
config.jsonkey, now empty, rather than dropping it. Thesubstrate backend injects that key as a secret-backed env var
(
agent_lifecycle.go→secretEnv("KAGENT_CONFIG_JSON", secretName, "config.json")),and removing the key would break that reference. An empty value is inert on both
paths: the volume isn't mounted for BYO, and
_config_materialize.pyskipsmaterializing empty values.
/configvolume, because they needsrt-settings.json. Theirconfig.jsonstays empty. There's a test for this.2.
omitemptyonAgentConfig.Model(go/api/adk/types.go) — defence in depth,not the fix. Please don't let this one line stand in for the change above:
config.json{"model":null,…}(before)type='model_attributes_type'— "Input should be a valid dictionary or object…"{"description":…}(withomitemptyonly)type='missing'— "Field required"The file still exists and is still mounted; only the error changes.
omitemptyisworth having because a nil interface should not serialize to
nullin the first place,and it's the part that still applies on
main, whereAgentConfigsurvives but thisBYO branch does not.
AgentConfig.UnmarshalJSONalready treats an absent model thesame as a null one, so the config still round-trips.
Why this branch
mainwon't help anyone here.26732e86(#2565) deletedgo/core/internal/controller/translator/outright;git tag --contains 26732e86returns nothing and that commit is not on
release/v0.10.x. Onmainthere is noAgentreconciler, so a BYOAgentisn't reconciled at all.Meanwhile
v0.10.0-rc3is the newest tag and the newest published image, with no0.10.0final and norc4—rc3is what users are running today.release/v0.10.xhas unreleased commits past
rc3, none touching this code(
git diff v0.10.0-rc3 origin/release/v0.10.x -- .../translator/agent/is empty).Happy to also open a
mainPR carrying just theomitemptychange if you'd like thetwo branches consistent.
Tests
Added to
manifest_builder_test.go, alongside the existingneedsSRTSettingscoverage:TestNeedsAgentConfig— the predicate itself.TestBuildConfigSecret_BYOOmitsAgentConfig— the regression guard: a BYO agent getsan empty
config.json, no volume and no mount.TestBuildConfigSecret_BYOWithSandboxMountsOnlySRTSettings— sandboxed BYO stillgets
/configforsrt-settings.json, withconfig.jsonstill empty.TestBuildConfigSecret_DeclarativeKeepsAgentConfig— pins the declarative path.I verified the regression guard actually catches this: reverting just the
needsAgentConfigcall tocfg != nilfails both BYO tests withconfig.json = "{\"description\":\"A BYO test agent\",\"instruction\":\"\"}"— whichis also the concrete demonstration that
omitemptyalone leaves aconfig.jsoninplace.
The
byo_agent.jsongolden fixture is regenerated (UPDATE_GOLDEN=true). It had beenrecording the
"model": nullconfig and the/configmount as expected output, whichis why nothing flagged this. Note the fixture's
kagent.dev/config-hashgoes to"0",matching the pre-
b04769e8empty hash input; existing BYO pods will roll once onupgrade.
Results
gofmt -lclean andgo vetclean on all three touched files.One note on linting:
make -C go lintdoes not run in my environment — thelocally-built
kube-api-linterplugin fails to load withfatal error: runtime: no plugin module data(a Go plugin ABI mismatch against thego1.26.5 toolchain). I confirmed this reproduces identically on an unmodified
release/v0.10.xworktree, so it's environmental and not caused by this change. As asubstitute I ran
golangci-lint run --no-configover the two touched packages: itreports 3 pre-existing
errcheckfindings, all in files this PR does not touch(
api/adk/types_test.go:725-726,adk_translator_golden_test.go:47) and none in thechanged code. Worth a maintainer confirming CI lint passes.