Repository navigation
Conversation
Add an optional `authorizationFrom` Secret key reference to AgentTemplate `skills[].source.git`. The compiler binds the key to the repository hostname as a Substrate egress gateway credential and the runtime sends a placeholder `Authorization` header for the gateway to replace, so the token never enters the Actor, its configuration, or a snapshot. A gateway credential only applies once an Actor has egress, which Substrate activates after readiness and never grants the golden Actor (kagent-dev#2604). Runtimes therefore fetch standalone skills before the first task of a session Actor instead of at startup: the Go ADK, Claude and Codex runtimes call Materialization.EnsureSkills per task, and a fetch failure fails that task with a `skills_unavailable` message instead of crashing the golden Actor. Plugins keep their startup fetch and reject `authorizationFrom`. Refs kagent-dev#2604, kagent-dev#2815 Signed-off-by: acherifi <ali.cherifi@homagames.com>
|
We hit the same failure on a Substrate line with replace-only A few notes:
I'll close #2899 in favour of this one. The full-header Secret is the better shape. |
|
Tested this PR on a three-node K3s cluster with kagent v1.0.0-alpha9 ( Before (stock alpha9 harnesses). An AgentTemplate that selected skills through With this PR. The same templates use standalone
Not covered: We now run these patched harness images in our development environment until a release includes this fix. Thanks for working on it! |
Problem
A v1alpha3 AgentTemplate cannot use a skill from a private Git repository:
skills[].source.githas no credential, and the runtime runs a plaingit fetch(#2815).Adding a credential field does not fix this on its own. Every Git, OCI or S3 skill, public or private, fails today while the golden Actor starts (#2604), for two independent reasons in Substrate v0.4.0-alpha1:
ateom-gvisorRunWorkloadactivates the Actor's egress tunnel only after the wakeup probe passes (cmd/ateom-gvisor/main.go:wakeupprobe.WaitAll, thens.tunnel.Activate). Until then atunnel closes every outbound connection. The runtimes fetch skills before they serve/readyz.ate-goldenatespace. Substrate's template reconciler creates it, and kagent installs per-Actor egress policies only on session Actors (ApplyActorTransition). atenet therefore denies the golden Actor's requests with "actor has no egress policy". Credential injection could not resolve there either: the credential provider authorizes Secrets per atespace (see feat(substrate): the compiled ActorTemplate carries the revision's egress policy as its default #2942).The Substrate egress gateway is the right place for the credential: it keeps the token out of the runtime, the same way model keys and
RemoteMCPServer.headersFromare handled. The fetch just has to happen when the gateway is in the path.Change
API (additive, optional).
GitArtifact.authorizationFromis aSecretKeyReference(name,key; no namespace) to a Secret in the AgentTemplate's namespace. The Secret value is the completeAuthorizationheader, for exampleBasic base64(x-access-token:TOKEN)for GitHub, orBearer TOKEN. This follows the existingheadersFromconvention that the Secret holds the full value. CEL rules require anhttpsURL, and reject the field onplugins[].source.git(see Limits). CRDs, deepcopy and the Helm CRD copies are regenerated.Compiler.
CompileCredentialsbinds each such key to the repository hostname asauthorization(ate-secret://k8s.io/default/<template namespace>/<secret>/<key>). It walks Shared child templates as well.CompileSkillResourcesalready adds the repository origin to the derived egress allow-list. The binding goes throughegress.CanonicalCredentials, so two different Secrets for one Git host are rejected, and so is a conflict with a model or MCP credential on that host. URLs with user info are rejected. The runtime contract (agentplugin.GitSource) gains onlygateway_authorization: true. It does not name the Secret.Runtime.
skillsinit.CloneGitCommitrunsgit -c http.extraHeader="Authorization: kagent-credential-injected" fetch …for these sources. Substrate's gateway replaces only headers a request already carries (applyEffects), so a placeholder is required. It is passed per command, so nothing is written to.git/config. All git invocations now setGIT_TERMINAL_PROMPT=0, so a missing credential fails at once instead of waiting for a username prompt.agentplugins.Materializeno longer fetches standalone skills. It validates them, still fetches plugins, and returns aMaterializationwhoseEnsureSkills(ctx)fetches the standalone skills. That call is idempotent and safe for concurrent use. A failure resumes at the failed skill on the next call.KAgentExecutor), Claude and Codex (ProcessDriver.Run) runtimes callEnsureSkillsbefore every task. Once the skills are present the call does nothing. The ADK always wires the skill toolset when standalone skills are configured;skilltoolsetlists the directory on every model request.Can the secret end up in a golden snapshot?
No, for two reasons:
EnsureSkillsnever runs there: the snapshot contains neither the credential nor the private skill content. The first task of each session Actor does the fetch. By then networking is active and kagent has installed that Actor's egress policy and credential binding (created suspended, then policy, then resume). The fetched files end up in that session's own snapshots. Those hold skill content, never the credential.Behavior changes
SKILL.md) fails the task instead of crashing the golden Actor. The task gets afailedstatus whose message isskills_unavailable: materialize skill "<name>": …, and the runtime logs the error ("standalone skills are unavailable" in the ADK). The next task retries. The revision itself becomes ready, because readiness no longer depends on the network.skills[], not forplugins[].Limits
authorizationFromis rejected on plugins by CEL and by the compiler.credentialProvider.namespacePolicies), like other gateway credentials.Relation to other work
feat(api): a per-source credential for private git skill and plugin sources #2899 (draft, giantswarm) adds a similar
credentialRefrouted through the gateway. It has three differences:base64(user:token)and the compiler addsBasic. Here the Secret holds the full header value, following theheadersFromconvention, which also allowsBearer.http.extraHeaderplaceholder, with no environment variable and no Secret-backedEnvVar.Happy to rebase onto feat(api): a per-source credential for private git skill and plugin sources #2899's API shape if maintainers prefer it.
[FEATURE] Private skill and plugin sources in the v1alpha3 API #2815 proposes fetching in the controller and mounting the result as an image volume. That design is blocked on Substrate (anonymous image-volume pulls, atelet: pull image credentials from the kubelet credential provider agent-substrate/substrate#917). This PR needs no Substrate change and does not preclude it.
Closes the standalone-skill half of [BUG] Git, OCI, and S3 Agent Plugin resources cannot be materialized while creating Substrate golden Actors #2604; refs [FEATURE] Private skill and plugin sources in the v1alpha3 API #2815.
Tests
api/v1alpha3: envtest CRD validation (TestConfigurationCRDValidation/git_skill_authorizationFrom). Cases: valid skill; missing key →Required value; empty key;namespace→unknown fieldunder strict field validation;http://URL → rejected; plugin → rejected.core/internal/translator: binding plus egress host and contract flag (TestCompilePrivateGitSkillBindsGatewayCredentialAndEgress), a Shared child template namespace, and conflicts, user info and plugins rejected.core/internal/skillsinit:TestCloneGitCommitThroughCredentialGatewayruns agit http-backendbehind anhttptestserver that requiresAuthorization. In front of it sits a fake gateway that, like Substrate, replaces the header only when it is present. Cases: placeholder → fetch succeeds and.git/configholds nothing; no placeholder → fails fast; placeholder sent directly → 401.core/pkg/agentplugins:Materializedoes no standalone fetch;EnsureSkillsfetches, resumes after a partial failure without refetching, and is a no-op on a zero value.adk/pkg/a2a: a failure produces afailedstatus withskills_unavailable, and the agent is not invoked; it runs once skills are present.adk/pkg/config, and theclaude/codexdriver and adapter tests, are updated for the deferred fetch.go build ./...,go vetandgo testpass for./api/... ./adk/... ./harness/... ./core/pkg/... ./core/internal/{skillsinit,translator/...,substrate,egress}.Not verified: an end-to-end run on a kind cluster with Substrate (
make -C go e2e). There is no existing e2e coverage for skills; it would need a private Git fixture behind a DNS name. I have not observed the first-task fetch through the real gateway.