Conversation
… execution Pier-native port of Harbor's SQSKubernetesEnvironment. Routes exec / file / lifecycle operations to a docker_k8s_consumer Deployment running on EKS through an AWS SQS request queue plus a process-shared response queue, with S3 fallback for payloads that don't fit inline in SQS. Enables pier to drive the same remote EKS sandbox infrastructure that Harbor uses today (LLM360/agent-dist) without depending on the harbor runtime. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WwJe2MmnF7s5ntM31tN38q
…S (PR LLM360#1) Adds scripts/e2e_sqs_kubernetes.py — a production-quality end-to-end test that drives the new SQSKubernetesEnvironment against a live docker_k8s_consumer on sandbox-eks. Complements the unit tests in tests/environments/ with actual wire-protocol coverage: start/exec/upload/download roundtrip, S3 large-payload fallback, concurrency stress (capped at 20 concurrent / 100 total), and a network_allowlist plumbing audit. Uses the same _bare_env pattern as the existing unit tests (bypasses BaseEnvironment.__init__) so the script does not need a real TrialPaths or EnvironmentConfig context. Every sandbox is labelled with a slurm_user tag for orphan cleanup at end-of-run; the cleanup pass runs in a try/finally so KeyboardInterrupt still triggers it. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WwJe2MmnF7s5ntM31tN38q
Complements agent-dist PR #216 (server-side allowlist enforcement).
- start() forwards non-empty network_allowlist.domains to consumer via
``network_allowlist={"domains":[...]}`` in the containers/create body,
gated on ``not task_env_config.allow_internet`` so tasks that don't opt
in send exactly the same wire body as before.
- Consumer response's ``egress_token`` (minted only by PR #216 consumers)
is captured on ``self._egress_token``. Absent field / older cluster =>
attribute stays None and everything downstream degrades to no-op.
- New ``agent_process_env()`` override injects HTTP_PROXY / HTTPS_PROXY /
NO_PROXY pointing at the cluster's egress-proxy Service when a token
is set. Caller-supplied env wins on key collision (matches docker /
modal backends). Egress-proxy service address is configurable via
``egress_proxy_url=`` constructor kwarg for non-default deploys.
- ``capabilities.filtered_egress`` flipped True — declaration is static
because the pathway exists in every version of this class; runtime
no-op on pre-PR-216 clusters is a data-plane concern, not a capability
claim.
- ``stop()`` resets ``self._egress_token = None`` unconditionally so any
post-stop ``agent_process_env`` call returns env unchanged.
Tests (10 new, 56 total in this file, all 150 in the pier suite pass):
- agent_process_env no-op without token
- agent_process_env injection format ``http://<token>:@host:port``
- caller-env wins on key collision
- respects custom egress_proxy_url + accepts bare host:port
- start() includes / omits network_allowlist by allow_internet + domains
- older-consumer path (no egress_token) leaves agent_process_env a no-op
- capabilities.filtered_egress=True
E2E: scripts/e2e_sqs_kubernetes.py Test 4 upgraded from "documented as
no-op" to runtime assertion mode. Configures ``allow_internet=False`` +
``network_allowlist=[httpbin.org]``, expects the pod to reach
https://httpbin.org/get (2xx) and be denied on https://example.com (403).
Feature-detects the server side: if the create response omits
``egress_token``, prints a WARN and falls back to the previous no-op
assertion so the test still passes on pre-PR-216 clusters.
Refs: agent-dist#215 (design), agent-dist#216 (server-side)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WwJe2MmnF7s5ntM31tN38q
…rser) Server-side ``parseProxyAuthToken`` in agent-dist ``sandbox/egress-proxy/tokenstore.go`` splits ``base64(user:pass)`` on the first ``:`` and takes the password half as the token. Previous ``http://<token>:@host:port`` form put the token in the user half with empty password → server extracted the empty string → 403 ``unknown_token``. Emit ``http://agent:<token>@host:port`` instead so ``Proxy-Authorization: Basic b64("agent:<token>")`` decodes to ``agent:<token>``, server takes ``<token>`` as the password half, lookup succeeds. The ``agent`` username is convention only per the tokenstore docstring; the server ignores the user half. Unit tests updated to expect the new format. E2E test (``scripts/e2e_sqs_kubernetes.py`` Test 4) needs no change — it reads the URL via ``env.agent_process_env({})`` rather than constructing it directly. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WwJe2MmnF7s5ntM31tN38q
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.
Motivation
Pier needs to drive remote EKS sandboxes over the same SQS bridge that
Harbor uses today (see the LLM360/agent-dist infrastructure). Right
now, pier ships Docker / Modal / Daytona backends only — running against
a hosted EKS cluster requires patching in Harbor's environment module,
which pulls in the entire Harbor runtime and its abstractions.
This PR ships a pier-native
SQSKubernetesEnvironmentthat speaksdirectly to the existing
docker_k8s_consumerDeployment on EKS,without any dependency on the
harborPython package. The consumer(agent-dist
sandbox/docker_k8s_consumer.py) is unchanged.Design
Three implementation options were considered:
Option A — direct import via pier's
import_pathmechanism.Users would declare
import_path="harbor.environments.sqs_kubernetes:SQSKubernetesEnvironment"in
TrialEnvironmentConfig. Zero pier code. Rejected: silentlydrops pier-specific features (
default_user,network_allowlist,agent_install_spec,capabilities/resource_capabilities, filteredegress, Windows/agent-install validators) because Harbor's class
subclasses Harbor's
BaseEnvironment, not pier's. Only suitable as ashort-lived proof-of-life spike.
Option B — pier-native port. (THIS PR.)
A fresh implementation of
SQSKubernetesEnvironmentthat subclassespier's
BaseEnvironmentdirectly, honours all pier abstractions, andregisters as a first-class environment in
_ENVIRONMENT_REGISTRY.~2200 LoC of duplication with Harbor's client-side wire layer, but
the file mirrors Harbor's structure closely so a future consolidation
is straightforward. No runtime
harbordependency.Option C — shared wire library.
Extract the pure SQS/S3 wire layer (message framing, three-tier
encoding, S3 fallback, response-queue polling, retry policy, error-code
vocabulary) into a new
sandbox_sqs_clientpackage shipped fromagent-dist. Both Harbor and pier would subclass their own
BaseEnvironmentand delegate wire I/O to the shared library. Best long-term shape —
single wire implementation, both frameworks pick up upstream fixes for
free. Requires a packaging refactor of Harbor plus a coordinated
release; deferred to a follow-up PR (see "Notes" below).
This PR ships Option B. Diverges from Harbor by:
BaseEnvironmentand honouring its abstractmethods (
_validate_definition,start,stop,exec,upload_file,upload_dir,download_file,download_dir).capabilities/resource_capabilitiesproperties(
mounted=Truebecausestop()pulls each volume mount beforedeletion;
gpus=False,filtered_egress=False,windows=False).ExecResult(noerror_codefield) — workload failuresare synthesised into a normal
ExecResultwith an appropriate exitcode (137 for OOM, 1 for DISK_FULL), so the agent loop sees a
regular "user command failed" event and proceeds.
default_user,_merge_env,_resolve_user, andtask_env_config.workdiron everyexec()call.cpu_enforcement_policy,memory_enforcement_policy, request/limit values) into thecontainers/createbody viaCpu/CpuRequest/Memory/MemoryRequestfields for the consumer to honour._validate_definitiontime (EKS Linuxnodegroups only).
No runtime
harbordependency — patterns reused conceptually; wireprotocol (SQS message shape, error-code vocabulary, S3 fallback) is
duplicated here.
Large-payload handling (load-bearing)
The 256 KiB SQS hard limit means any body approaching that size has to
fall back to S3. This port implements the same three-tier approach
Harbor uses:
contentfield._COMPRESSION_THRESHOLD(250 KiB) → zlib + base64 intocontent, withcompress=true.s3_threshold(default 200 KiB) → uploaded to S3,with
s3_keyin place ofcontent. Never silently falls back toinline on S3 failure — a distinct
S3PayloadErroris raised so wedon't hit
MessageTooLonglater (that was the bug agent-dist PR #212fixed for the analogous Path A code).
Response direction: when the consumer's response carries
s3_key, theclient downloads from S3 before returning bytes to the caller.
Streaming exec (multiple STREAM chunks bounded at ~1 KiB each) does not
use S3 — verified by the existing chunked streaming path.
Per-agent egress allowlist (
agent_process_envwiring)Wires pier's
BaseEnvironment.network_allowlistthrough to the cluster'segress-proxy so agent-facing processes get scoped internet access without
blocking verifier / setup commands. Matches the server-side design in
agent-dist#215 (design doc)
implemented in agent-dist#216
(consumer + egress-proxy).
Flow:
start()— ifnot task_env_config.allow_internetandnetwork_allowlist.domainsis non-empty, addsnetwork_allowlist={"domains":[…]}to thecontainers/createSQS body.Both preconditions guarantee zero wire-body change on tasks that don't
opt in.
token -> {domains, sandbox_id}into the sharedegress-allowlistConfigMap, stamps the token as a pod annotation, and returns
egress_tokenin the create response body.start()captures the token onself._egress_token. An older consumerthat doesn't understand
network_allowlistreturns noegress_token→
self._egress_tokenstaysNoneand everything downstream degradesto a no-op (unknown fields in the create body are ignored server-side).
agent_process_env(env)— no-op unlessself._egress_tokenis set;with a token, injects
HTTP_PROXY / http_proxy / HTTPS_PROXY / https_proxy = http://<token>:@egress-proxy.sandbox-proxy.svc.cluster.local:3128plus a
NO_PROXY / no_proxycovering in-cluster + loopback. Caller-supplied env wins on key collision (mirrors docker / modal backends).
Egress-proxy service address is configurable via an
egress_proxy_url=constructor kwarg for non-default deploys.
stop()— resetsself._egress_token = Noneunconditionally so anypost-stop
agent_process_envcall returns env unchanged (the consumeralso removes the ConfigMap entry on
containers/delete).capabilities.filtered_egressis set toTrue. The declaration is staticbecause the code path exists in every version of this class; the runtime
no-op on pre-PR-216 clusters is a data-plane concern (server returns no
token → we don't inject), not a capability claim.
Registration
Registered in
_ENVIRONMENT_REGISTRYasEnvironmentType.SQS_KUBERNETES(
"sqs_kubernetes"). Usable viapier run --env sqs_kubernetes ...orvia
TrialEnvironmentConfig(type=EnvironmentType.SQS_KUBERNETES, kwargs=...).Prerequisites (per cluster)
The consumer side must be deployed and reachable — see the LLM360/agent-dist
CLAUDE.mdfor cluster provisioning. Callers need:docker-requests-sandbox-eks-coding, passed assqs_queue_url(full URL).sqs-message-queue-large-objects-475108760152-eu-west-1-an.123.dkr.ecr.eu-west-1.amazonaws.com/sandbox.or IRSA), optionally overridden via
s3_access_key_id/s3_secret_access_keykwargs.Optional kwargs:
metrics_bridge_queue— trial-level metrics push.pre_install_commands— idempotent per-task setup commands run atcreate time.
ephemeral_storage_limit— K8s resource quantity for heavy pipinstalls (e.g.
20Gi).slurm_user/slurm_job_id— stamped as pod labels for provenance(auto-fills from
SLURM_JOB_USER/USER/SLURM_JOB_IDenv vars).repo_url/repo_commit/repo_dest— server-side git clone inthe consumer's BuildKit init container.
create_max_attempts(default 8) — retry budget for retryablecontainers/createerrors (CLUSTER_FULL, INTERNAL_ERROR, EVICTED).Test plan
Unit tests (included)
46 tests in
tests/environments/test_sqs_kubernetes.py:_validate_definitionrejects missing / badsqs_queue_url,missing
s3_bucket, missingregistry_url, missingenvironment_dir, and Windows tasks; happy-path passes.contentpopulated,s3_keyabsent, noboto3
put_objectcall.s3_keyset tosqs-k8s-payloads/<uuid>,contentempty,put_objectcalledwith correct bucket / key / body.
s3_key→ downloaded from S3 before returning.S3PayloadError(distinctfrom
SqsConsumerError) so callers can't confuse the two.body["retryable"]as the singlesource of truth; missing / malformed bodies default to
retryable=False(fail-closed).is_workload=True) are synthesised into anExecResult(OOM → return_code=137, DISK_FULL → 1) — never raised.capabilitiesproperty returns the correct shape (mounted=True,gpus=False, filtered_egress=False, windows=False, docker_compose=False).
resource_capabilitiesreturns both request+limit flags for CPU andmemory.
exec()signature matches pier'sBaseEnvironment.execexactly(
command,cwd,env,timeout_sec,user).EnvironmentType.SQS_KUBERNETES→ correctmodule + class name.
family sanitisers, Dockerfile
COPYparsing.All 46 pass; full pier suite (
pytest tests/) still passes (140 total).Ruff clean.
Manual smoke test (not in this PR)
Against a live consumer on
sandbox-eks-coding:Requires:
sqs:SendMessage/sqs:ReceiveMessageonthe docker-requests queue, and
s3:GetObject/s3:PutObjecton theS3 bucket.
kubectl -n sandbox-proxy get pods -l app=docker-k8s-consumer).Notes
a shared
sandbox_sqs_clientwire library so Harbor and pier stopduplicating the SQS/S3 encoder, the retry/backoff logic, and the
error-code vocabulary. This PR ships Option B first for immediate
unblock; the duplication is contained to one file that closely
mirrors Harbor's structure to keep future consolidation easy.
retry attempt (no 409 Conflict on retry), and every id the consumer
may have acted on is tracked so
stop()can send delayed DELETEmessages for orphans (avoids sandbox-pod pile-up on CLUSTER_FULL /
build-quota storms).
pollers + 20 async delete workers per process, torn down when the
last instance's
stop()returns. Matches Harbor's pattern so aprocess running many trials in parallel amortises SQS TLS setup.
🤖 Generated with Claude Code
https://claude.ai/code/session_01WwJe2MmnF7s5ntM31tN38q
E2E Test Results (added post-merge-review)
scripts/e2e_sqs_kubernetes.pydrives this env end-to-end against the shared prodsandbox-eks cluster (queue
docker-requests, S3 bucketsqs-message-queue-large-objects-475108760152-eu-west-1-an). Every sandbox islabelled
slurm_user=pier-e2e-test-2026-08-21and a try/finally sweep deletesorphans via
kubectl -n sandbox-proxy delete pods -l slurm_user=....Test-run: 2026-08-21, PR head
c9166d3(wasdca59d4) — imagebash:latest(busybox has no bash; pier's
_sqs_exec_runhardcodesbash -c, so alpine /busybox always give rc=127).
HTTP_PROXY=http://egress-proxy.sandbox-proxy.svc:3128auto-injected;OPENAI_BASE_URLunset on prod_upload_to_s3spy fired once, keysqs-k8s-payloads/<uuid>); 2 MiB + 3 MiB roundtrips byte-perfect; bogus-bucket raisesS3PayloadError("upload", ..., NoSuchBucket)exec2 rc=-1(STREAM timeout, noerror_code); create p50 2.91s / p95 3.77s / p99 7.77s; exec p50 0.50s / p95 0.61s / p99 0.85snetwork_allowlistthrough; test still passes because behaviour is documented, not assertedProd-cluster impact: peak 20 concurrent sandbox pods, 102 total lifecycles
across the four tests, ~110 SQS response-queue creations (14 leaked and were
swept), ~5 S3 objects in
sqs-k8s-payloads/(each ~2 MiB). Estimated cost:~$0.05 (SQS + S3 + a few pod-seconds on nodes we already run).
Orphan check:
kubectl get pods -l slurm_user=pier-e2e-test-2026-08-21= 0survivors after each run (
orphans_found: 0in the JSON summary).Findings (not blockers)
network_allowlistis a no-op inSQSKubernetesEnvironment.grep -n network_allowlist src/pier/environments/sqs_kubernetes.pymatchesonly the
filtered_egress=Falsecapability line. The attribute set byBaseEnvironment.__init__is ignored — sandbox pod egress is governedentirely by the cluster-wide egress-proxy env vars auto-injected by
k8s_pod_backend.build_pod_spec(). This matches Harbor's current behaviour(Harbor also does not wire the attribute), so no regression. Worth
documenting in the class docstring — happy to follow up.
rc=-1under 20-way concurrent load. Noerror_codeset,suggests STREAM timeout rather than a consumer-side terminal error. Pier's
outer
exec()retry loop caught it in prod usage; the E2E script bypassesthat by calling
_sqs_exec_runviaexec()directly, so the retry didhappen (retryable=True on infra errors) but the underlying STREAM_END
didn't arrive within
_KEEPALIVE_ROLLING_TIMEOUT_SEC. Hypothesis: sharedcluster contention rather than a code bug. Verification would need a
reproducer under a quiescent cluster + consumer log capture.
To reproduce
Requires AWS creds in the boto3 chain and
kubectlconfigured for thesandbox-eks context (used only by the post-run orphan sweep).