Repository navigation
feat(sandbox): task-scoped cloud credential so secret-rotation can run sandboxed - #30
Merged
isadominguez314 merged 3 commits intoSep 11, 2026
Merged
Conversation
Every agy run has been dying at startup. The matrix sets one AGENT_MODEL for the whole run -- gemini-3.1-pro-preview, spelled for Vertex, which needs that suffix -- and agy rejects the -preview suffix outright and refuses any selection that does not name a reasoning tier exactly once. _resolve_model_name only stripped the provider prefix, so the Vertex id reached the command line verbatim and agy exited 1 in about two seconds with no conversation DB and no transcript. Not a sandbox problem, though that is where it surfaced: an ambient run fails identically, and has since the harness was written. Normalize inside this harness rather than respelling AGENT_MODEL for the agy arm. That value labels every arm in the matrix and feeds the judge, so bending it to suit one CLI would desynchronize that arm from the runs it is compared against, and Vertex needs exactly the suffix agy refuses. Emit the bare slug plus --effort: it is the one form that cannot collide, and no lookup table means nothing to maintain as the catalogue grows -- agy validates the result and still fails loud on anything unknown. The tier is a scoring variable rather than a formatting detail, since low and high are materially different agents and agy has no untiered form. Default it to high, because the other harnesses run their model with no reasoning throttle and anything lower would hand the agy arm a handicap that reads as a capability gap. AGENT_MODEL_EFFORT overrides it, and an unknown tier is rejected on the spot rather than passed through to fail later as agy's own error mid-arm. settings.json now carries the same resolved spelling as the flag. agy does not validate modelConfigs.defaultModel -- an unknown value there is ignored in silence and the run quietly falls back to another model -- so the flag is the only guard, and it should not be guarding a value that settings contradicts. Drop GEMINI_MODEL from the env overlay. It is a Gemini CLI variable that agy ignores: a garbage value raises no error and a valid one does not change the model the run reports. It looked like a lever and was not. Spellings verified against agy 1.2.0 on the bastion, in the pinned image and under the executor's real invocation.
…n cloud calls A sandboxed agent has no ambient cloud identity, by design — so a task whose work includes cloud API calls beyond kubectl (secret-rotation must add a Secret Manager secret version) could previously only run unsandboxed. Close that gap without widening the boundary: - The task's stack provisions a run-unique service account holding only the roles the task needs, scoped to the resources it provisioned, and names it in an agent_cloud_identity output. The provisioning identity gets serviceAccountTokenCreator on that account in the same stack, so the whole loop tears down with the run. - The GCP provider impersonates that account host-side and mints a short-lived access token; a mint failure fails the run loudly rather than letting the agent be graded on a missing credential. - The executor injects the minted env by value (CLOUDSDK_AUTH_ACCESS_TOKEN, GOOGLE_OAUTH_ACCESS_TOKEN, project id), after the overlay filter and before the container-owned vars, which still win any duplicate. Tasks that declare no agent_cloud_identity output are unaffected: nothing extra is minted and nothing extra crosses.
…xplicitly The ADC userinfo derivation reads a null email on a VM service-account credential that lacks the userinfo-email scope, which failed the whole apply. An explicit token_creator_member variable now wins when set; the derivation stays as the fallback, and with neither available the binding is skipped so the harness's mint fails loud naming the missing grant.
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.
Stacked on #28 (
sandbox/agy-model-ids): the first commit here is #28's; review only the top commit. Rebase/retarget after #28 lands.The gap
secret-rotation is exempted from the sandbox because rotating the credential is a Secret Manager write, and a sandboxed agent has no cloud identity to make it with: the boundary strips
CLOUDSDK_CONFIG/GOOGLE_APPLICATION_CREDENTIALSby design, the metadata endpoint is blocked on the bastion, and the org'sdisableServiceAccountKeyCreationconstraint means there is no key file to mount. The scoped-ServiceAccount kubeconfig covers kubectl, but not the cloud API call the task is actually about.What lands
Stack side. The secret-rotation stack provisions a run-unique
rot-<ns>-<hex>service account holding exactly two roles —secretmanager.secretVersionManagerandsecretmanager.secretAccessor— scoped to exactly this run's secret, and names it in a newagent_cloud_identityoutput. The provisioning identity is grantediam.serviceAccountTokenCreatoron that account in the same stack (an SA-level binding, not a shared project-level one, so concurrent runs cannot fight over it at teardown), and the whole loop tears down with the run.Harness side. A new
Provider.sandbox_cloud_credential_envseam (default: nothing crosses). The GCP provider reads the output, impersonates the account host-side viagcloud auth print-access-token, and returnsCLOUDSDK_AUTH_ACCESS_TOKEN/GOOGLE_OAUTH_ACCESS_TOKENplus the project id. The executor injects these spec-owned values after the overlay filter; container-owned names are still skipped andHOME/KUBECONFIGstill win any duplicate-e. A mint failure raises — a failed record, never a silent run without the credential the task depends on.Properties. No key file exists and none is mounted; the operator's ADC never crosses; the token lives ≤1h and is not refreshed, so an agent still calling past that gets a clean 401. Tasks with no
agent_cloud_identityoutput are byte-for-byte unaffected.What this does not solve (known, separate)
requires_unsandboxedexemption — flip it there once this and feat(sandbox): wire antigravity, claude_code and openclaw onto the sandbox seam #14 are both in), and the agy image is build(docker): add the Antigravity CLI sandbox image #27. That image ships nogcloud; either add the SDK layer there or accept that the agent drives Secret Manager via the REST API with the injected token.claude_coderemains feat(sandbox): give a sandboxed Vertex run its own metadata-server credential #25's territory; agy does not need it (its OAuth token rides the workspace mount).Validation
tofu validateon the modified cluster module: clean. The root secret-rotation stack has a pre-existing validate-time cycle in the shared vcluster module arm, present on the base commit before this change (verified by validating the untouched base tree).tokenCreatorgrant to prove end to end; suggested smoke: provision the stack, thengcloud auth print-access-token --impersonate-service-account=$(tofu output -raw agent_cloud_identity)as the runner identity.