Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/voltage-agent-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ jobs:
python3 test-codex-login.py "$IMAGE"
python3 -O test-profile.py "$IMAGE"
python3 test-cluster.py "$IMAGE"
python3 test-environment.py "$IMAGE"
python3 test-skills.py "$IMAGE"

# main: resolve engineering-skills master once, so every platform image of
Expand Down
1 change: 1 addition & 0 deletions voltage/images/buzz-agent/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
# Build from the repository root so the image packages the checked-out Buzz
# source; Dockerfile.dockerignore next to this file selects the context.
FROM rust:1.95-alpine3.22@sha256:064dfc925d68d1a63f4fd2871bd7dc6e6ea56692989a487185855d62885d90aa AS source
RUN apk add --no-cache bash build-base cmake git musl-dev openssl-dev \

Check warning on line 5 in voltage/images/buzz-agent/Dockerfile

View workflow job for this annotation

GitHub Actions / Lint Dockerfiles

Pin versions in apk add. Instead of `apk add <package>` use `apk add <package>=<version>`
openssl-libs-static perl pkgconf protoc python3 procps
WORKDIR /build
ENV CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=2
COPY . .
FROM source AS package-checks
RUN apk add --no-cache tini \

Check warning on line 11 in voltage/images/buzz-agent/Dockerfile

View workflow job for this annotation

GitHub Actions / Lint Dockerfiles

Pin versions in apk add. Instead of `apk add <package>` use `apk add <package>=<version>`
&& tini -s -- cargo test --locked -p sprig -p buzz-acp -p buzz-agent -p buzz-dev-mcp -- --test-threads=1

FROM source AS builder
Expand All @@ -19,7 +19,7 @@

FROM node:22-trixie-slim@sha256:154ba2f4d6fec323d28e4f4bb86bba4677f1223391a1979cf521304e03a98dfa
LABEL maintainer="Voltage <hello@voltage.cloud>"
RUN apt-get update \

Check warning on line 22 in voltage/images/buzz-agent/Dockerfile

View workflow job for this annotation

GitHub Actions / Lint Dockerfiles

Pin versions in apt get install. Instead of `apt-get install <package>` use `apt-get install <package>=<version>`
&& apt-get install -y --no-install-recommends bash ca-certificates curl git gh gnupg openssh-client socat tini \
build-essential clang cmake pkg-config libssl-dev protobuf-compiler \
python3 python3-venv python3-pip golang-go postgresql-client sqlite3 \
Expand All @@ -33,7 +33,7 @@
# Release assets are public and pinned by checksum per architecture;
# credentials enter only at runtime.
ARG TARGETARCH
RUN case "$TARGETARCH" in \

Check warning on line 36 in voltage/images/buzz-agent/Dockerfile

View workflow job for this annotation

GitHub Actions / Lint Dockerfiles

Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
amd64) via=x86_64 gitleaks=x64 sccache=x86_64 \
kubectl_sha=874d5e72dbb819f43cff16bcd1e4f8bac5b7f2361fe1e55049b0a6c676fb0cbf \
via_sha=9e5a7f9384cacda29061d814e9397e7b6d90a72ee4663775362978ac9b65d8b7 \
Expand Down Expand Up @@ -98,6 +98,7 @@
# not change it; profile.py links each skill into the harnesses.
# hadolint ignore=DL3022
COPY --from=engineering-skills . /opt/engineering-skills/
COPY --chmod=0644 voltage/images/buzz-agent/environment.md /etc/buzz-agent/environment.md
Comment thread
thebrandonlucas marked this conversation as resolved.
Resolved
COPY --chmod=0755 voltage/images/buzz-agent/heavy-bin/ /opt/buzz-heavy/bin/
COPY --chmod=0755 voltage/images/buzz-agent/gh.sh /usr/local/bin/gh
COPY --chmod=0755 voltage/images/buzz-agent/via.sh /usr/local/bin/via
Expand Down
1 change: 1 addition & 0 deletions voltage/images/buzz-agent/Dockerfile.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,4 @@ voltage/
!voltage/images/buzz-agent/subagent.py
!voltage/images/buzz-agent/subagents.md
!voltage/images/buzz-agent/cluster.md
!voltage/images/buzz-agent/environment.md
3 changes: 3 additions & 0 deletions voltage/images/buzz-agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ python3 test-cache.py buzz-agent:local
python3 -O test-profile.py buzz-agent:local
python3 test-cluster.py buzz-agent:local
python3 test-skills.py buzz-agent:local
python3 test-environment.py buzz-agent:local
```

To build with the engineering skills, replace `--none` with a local checkout:
Expand Down Expand Up @@ -151,6 +152,8 @@ The bootstrap exports `KUBECONFIG` and appends [the diagnostic guide](cluster.md
It skips configuration when neither token is mounted, and rejects credentials for another developer namespace.
It performs no cluster API calls during startup.

Every session and worker also gets [the environment guide](environment.md), appended to the same instructions whether or not cluster credentials are mounted. It covers how to find enrolled services with `via capabilities`, how to detect a Sandbox, whose identity a `via` service uses, running untrusted code in a credentialed Pod, and where to report vulnerabilities.

The staging Terraform root owns the observer RBAC, worker projection, and API egress policy.
Diagnostic permissions cover reads across staging namespaces, including logs, events, metrics, routing, and Argo CD state.
They exclude Secrets, exec, attach, port forwarding, node/service proxies, and mutations.
Expand Down
19 changes: 19 additions & 0 deletions voltage/images/buzz-agent/environment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

## Working in the Sandbox

Before you say you lack access to a service, run `via capabilities` and `buzz --help`.
Enrolled services (for example Linear, Grafana and Argo CD) are reached with the capability that `via capabilities` lists for the service, usually `via <service> api ...`, not with a dedicated CLI or an environment variable.
Depot is different: the image wires it into build caches (for example `depot gocache`), so it has no `via depot api`; check the cache before calling it unavailable.
When you report that something is unavailable, say which places you checked.

Check that you are in a Sandbox with `KUBERNETES_SERVICE_HOST`, not with the path `/var/run/secrets/kubernetes.io`, which is not mounted in this Pod.
Comment thread
thebrandonlucas marked this conversation as resolved.
Resolved

A service reached through `via` may act as its own app identity or as a user, depending on how it is configured.
When you report a change made through it, such as a ticket you created, name the identity it used, or say that you do not know.

This Pod holds live credentials: a cluster token, your Buzz key, and `via` access.
Do not build, test, install dependencies for, or run code from a repository you do not trust.
Read its source, or ask the person you work for first.

Keep details of an unfixed vulnerability out of public issues, pull requests, commit messages and public channels.
Report them only where the person you work for has chosen, such as a private channel or the company tracker.
19 changes: 17 additions & 2 deletions voltage/images/buzz-agent/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,10 @@ def bootstrap():
env['CLAUDE_CODE_OAUTH_TOKEN'] = profile['models']['claude_oauth_token']
# The agent's tool shell starts from a cleared environment. Forward the model
# credentials so tools it runs, such as buzz-agent-review, reach the runtime;
# they stay in process environments and are never written to files.
# they stay in process environments and are never written to files. The
# Kubernetes host is not secret; the environment guide tells agents to check it.
env['BUZZ_ACP_MCP_FORWARD_ENV'] = ','.join(
name for name in (*INHERITED_MODEL_VARIABLES, 'CODEX_HOME') if env.get(name))
name for name in (*INHERITED_MODEL_VARIABLES, 'CODEX_HOME', 'KUBERNETES_SERVICE_HOST') if env.get(name))
step('configuring Git commit signing')
command('/usr/local/bin/buzz-agent-account', 'init', env=env)
return env
Expand All @@ -304,6 +305,7 @@ def restore_checkpoint():
SUBAGENT_GUIDE = Path('/etc/buzz-agent/subagents.md')
CLUSTER_CREDENTIALS = Path('/var/run/secrets/buzz-cluster')
CLUSTER_GUIDE = Path('/etc/buzz-agent/cluster.md')
ENVIRONMENT_GUIDE = Path('/etc/buzz-agent/environment.md')


def install_cluster_access(env):
Expand Down Expand Up @@ -336,6 +338,15 @@ def install_cluster_access(env):
handle.write(guide)


def install_environment_guide(env):
"""Give every Sandbox session the same rules for the services and credentials in its Pod."""
guide = ENVIRONMENT_GUIDE.read_text()
for target in (Path('/home/agent/.claude/CLAUDE.md'), Path(env.get('CODEX_HOME', '/home/agent/.codex')) / 'AGENTS.md'):
target.parent.mkdir(parents=True, exist_ok=True)
with open(target, 'a') as handle:
handle.write(guide)


def install_subagent_guide(env):
"""Tell Claude Code and Codex how to delegate when this session may spawn workers."""
if not (SUBAGENT_CREDENTIALS / 'token').exists():
Expand Down Expand Up @@ -403,6 +414,8 @@ def main():
install_cluster_access(env)
step('installing engineering skills')
install_skills(env)
step('installing the environment guide')
install_environment_guide(env)
step('starting the worker')
worker = ['/usr/local/bin/buzz-agent-cache', '/usr/local/bin/buzz-agent-worker']
os.execvpe(worker[0], worker, env)
Expand All @@ -412,6 +425,8 @@ def main():
restore_checkpoint()
step('configuring staging cluster diagnostics')
install_cluster_access(env)
step('installing the environment guide')
install_environment_guide(env)
step('installing the sub-agent guide')
install_subagent_guide(env)
step('installing engineering skills')
Expand Down
56 changes: 56 additions & 0 deletions voltage/images/buzz-agent/test-environment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Check that every session receives the environment guide, with or without cluster credentials."""
import subprocess
import sys

SCRIPT = r'''
import os, runpy, sys, tempfile
from pathlib import Path

profile = runpy.run_path('/usr/local/bin/buzz-agent-profile', run_name='probe')
install = profile['install_environment_guide']
settings = install.__globals__
home = Path(tempfile.mkdtemp(prefix='environment-test-', dir='/home/agent'))
claude = Path('/home/agent/.claude/CLAUDE.md')

def require(condition, message):
if not condition: raise RuntimeError(message)

guide = settings['ENVIRONMENT_GUIDE'].read_text()
require('via capabilities' in guide and 'KUBERNETES_SERVICE_HOST' in guide, 'guide lost its rules')
settings['CLUSTER_CREDENTIALS'] = home / 'no-worker'
settings['SUBAGENT_CREDENTIALS'] = home / 'no-primary'
claude.parent.mkdir(parents=True, exist_ok=True)
claude.write_text('existing instructions\n')
env = {**os.environ, 'CODEX_HOME': str(home / 'codex')}
install(env)
install(env)
Comment thread
thebrandonlucas marked this conversation as resolved.
Resolved
require(claude.read_text().startswith('existing instructions\n' + guide), 'Claude Code instructions were replaced or lack the guide')
require(claude.read_text().count('## Working in the Sandbox') == 2, 'each install call should append the guide once')
require((home / 'codex/AGENTS.md').read_text() == guide * 2, 'Codex instructions lack the guide')

# Bind the production seam: each startup path must install the guide before it launches its child.
main = settings['main']
for action in ('run', 'work'):
claude.write_text('')
events = []
class Launched(Exception): pass
def launch(path, argv, env): events.append('launch'); raise Launched()
settings.update(
step=lambda message: None, bootstrap=lambda: env, restore_checkpoint=lambda: None,
install_cluster_access=lambda env: None, install_subagent_guide=lambda env: None,
install_skills=lambda env: None,
install_environment_guide=lambda env, real=install: (events.append('guide'), real(env)))
settings['os'].execvpe = launch
sys.argv = ['profile', action]
try: main()
except Launched: pass
require(events == ['guide', 'launch'], action + ' did not install the guide before launching: ' + repr(events))
require(claude.read_text() == guide, action + ' did not write the guide')
print('PASS: the guide reaches Claude Code and Codex, and both startup paths install it')
'''

if __name__ == '__main__':
result = subprocess.run(['docker', 'run', '--rm', '-i', '--network', 'none',
'--entrypoint', 'python3', sys.argv[1] if len(sys.argv) > 1 else 'buzz-agent:local', '-'],
input=SCRIPT, text=True, timeout=90)
raise SystemExit(result.returncode)
5 changes: 4 additions & 1 deletion voltage/images/buzz-agent/test-profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ def rotate(models, git=None):
# Exactly the configured model credentials reach the agent's tool shell.
forwarded = set(filter(None, os.environ.get('BUZZ_ACP_MCP_FORWARD_ENV', '').split(',')))
models = {k for k, v in EXPECTED.items() if v and k in INHERITED}
if not models <= forwarded or forwarded - models - {'CODEX_HOME'}:
if 'KUBERNETES_SERVICE_HOST' not in forwarded:
raise RuntimeError('Kubernetes host is not forwarded to the tool shell')
if not models <= forwarded or forwarded - models - {'CODEX_HOME', 'KUBERNETES_SERVICE_HOST'}:
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
raise RuntimeError('forwarded tool-shell variables differ from the profile credentials')
if subprocess.check_output(['git','config','--global','user.email'], text=True).strip() != 'alice@example.invalid':
raise RuntimeError('Git identity missing')
Expand Down Expand Up @@ -177,6 +179,7 @@ def main():
'--tmpfs', '/tmp:uid=10001,gid=10001,mode=0700,size=128m,exec',
'-e', 'AWS_ACCESS_KEY_ID=testing', '-e', 'AWS_SECRET_ACCESS_KEY=testing',
'-e', 'AWS_DEFAULT_REGION=us-west-2', '-e', 'AWS_EC2_METADATA_DISABLED=true',
'-e', 'KUBERNETES_SERVICE_HOST=192.0.2.1',
'-e', 'AWS_ENDPOINT_URL=http://'+server+':5000', '--entrypoint', 'python3']
result = subprocess.run([*base, '-e', 'FIXTURE_PROFILE='+json.dumps(PROFILE), image, '-'],
input=SEED, text=True, capture_output=True, timeout=180)
Expand Down
Loading