Skip to content

Change(crd): rename 'Name' to 'name' in sandboxclaim_types CRD#440

Merged
k8s-ci-robot merged 3 commits intokubernetes-sigs:mainfrom
dongjiang1989:update-name
Mar 30, 2026
Merged

Change(crd): rename 'Name' to 'name' in sandboxclaim_types CRD#440
k8s-ci-robot merged 3 commits intokubernetes-sigs:mainfrom
dongjiang1989:update-name

Conversation

@dongjiang1989
Copy link
Copy Markdown
Member

@dongjiang1989 dongjiang1989 commented Mar 19, 2026

This change renames the field Name to name in the sandboxclaim_types CRD to follow Kubernetes naming conventions.

refer: #421 (comment)

Warning: This is a breaking change.
Existing resources and clients that reference status.Name will no longer work correctly and must be updated to use status.name instead.

Signed-off-by: dongjiang1989 <dongjiang1989@126.com>
@netlify
Copy link
Copy Markdown

netlify Bot commented Mar 19, 2026

Deploy Preview for agent-sandbox canceled.

Name Link
🔨 Latest commit baf9497
🔍 Latest deploy log https://app.netlify.com/projects/agent-sandbox/deploys/69be9e3274f0480008d750be

@k8s-ci-robot k8s-ci-robot added size/M Denotes a PR that changes 30-99 lines, ignoring generated files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. labels Mar 19, 2026
@dongjiang1989
Copy link
Copy Markdown
Member Author

/test presubmit-agent-sandbox-e2e-test

@k8s-ci-robot k8s-ci-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Mar 21, 2026
@k8s-ci-robot k8s-ci-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Mar 21, 2026
Signed-off-by: dongjiang1989 <dongjiang1989@126.com>
@dongjiang1989
Copy link
Copy Markdown
Member Author

dongjiang1989 commented Mar 21, 2026

/cc @janetkuo PTAL, thanks

Copy link
Copy Markdown

@codebot-robot codebot-robot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change correctly renames the field to lowercase name, addressing the JSON tag and aligning the CRD with Kubernetes schema conventions for sandboxclaim_types. However, this structural change has backward compatibility implications during rollout, as it will immediately break existing clients and potentially cause data loss during resource updates in existing clusters.

A few areas of concern should be addressed before merging:

  • Breaking Change Management: A deprecation strategy or backward-compatible parsing is highly recommended. Providing a deprecation window for Name in the v1alpha1 API schema could provide a seamless migration. Furthermore, the Python client update should include a fallback to parsing Name if name is missing to bridge the transition for older SandboxClaim resources.
  • Go Comment Style: Go documentation comments on exported fields should always begin with the exact name of the identifier (Name), regardless of the JSON representation, to comply with Go documentation standards.
  • Unrelated Changes: The updates to tools and indirect dependencies (such as golangci-lint, dev/tools/.custom-gcl.yaml, and dev/tools/go.mod) mix dependency updates with API changes. These can cause unrelated CI issues and merge conflicts, and should ideally be isolated to a separate PR.

(This review was generated by Overseer)

Comment thread extensions/api/v1alpha1/sandboxclaim_types.go
Comment thread k8s/crds/extensions.agents.x-k8s.io_sandboxclaims.yaml
@janetkuo janetkuo added the release-note-action-required Denotes a PR that introduces potentially breaking changes that require user action. label Mar 25, 2026
Copy link
Copy Markdown
Member

@janetkuo janetkuo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although it's a breaking change, it makes sure we follow k8s api convention before moving the API to beta. Also, it's a status field, so the impact is smaller than changing a field in spec.

@igooch given that it affects Python client, please take a look as well

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Mar 25, 2026
Copy link
Copy Markdown
Contributor

@igooch igooch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Mar 30, 2026
@k8s-ci-robot
Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: dongjiang1989, igooch, janetkuo

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot merged commit b11b876 into kubernetes-sigs:main Mar 30, 2026
11 checks passed
@dongjiang1989 dongjiang1989 deleted the update-name branch March 30, 2026 22:58
johannhartmann added a commit to mayflower/agent-sandbox that referenced this pull request Apr 8, 2026
Six PR-review findings folded into one commit because they all touch
the same K8sClient / K8sAgentSandbox pair and share test infrastructure.

Changes:

1. listSandboxClaims: validate label keys and values against the
   Kubernetes label regex before serializing into a labelSelector.
   Previously `${k}=${v}` concatenation meant a value like
   "alice,env=prod" would silently expand into a two-predicate
   selector -- which in deleteAll() would widen the delete set
   beyond what the caller intended. Keys and values that don't match
   the RFC-1123 forms now throw K8sAgentSandboxError("K8S_API_ERROR")
   at serialization time. Helpers extracted as serializeLabelSelector,
   validateLabelKey, validateLabelValue.

2. deleteAll: refuse empty labels without an explicit
   `{ confirmDeleteAll: true }` opt-in. The docstring used to warn
   that empty labels means "delete every claim in the namespace",
   but the signature didn't enforce it -- and since this method is
   the exact footgun that commit 88fbaf6 was fixing (label filter
   silently ignored), the type-level enforcement is warranted.

3. create: on initialFiles upload failure, call sandbox.close() in
   the outer catch so the `kubectl port-forward` subprocess is torn
   down, not leaked. The old failure path only deleted the claim and
   left the tunnel running. sandbox.close() handles the claim delete
   when deleteOnClose is set, so the outer cleanup becomes a
   best-effort safety net -- a duplicate delete against an already-
   gone claim 404s and is silently swallowed.

4. SandboxClaimStatus: correct the backcompat comment to cite the
   actual upstream PR kubernetes-sigs#440 (Change(crd): rename 'Name' to 'name') and
   the accurate root cause. The historical Go struct tag was
   explicitly `json:"Name,omitempty"` -- it wasn't omitted -- and
   PR kubernetes-sigs#440 renamed only the tag, not the Go field name.

5. Constructor branch coverage: three new tests verify the
   KUBERNETES_SERVICE_HOST environment detection logic added in
   3e2af35 actually selects the right loader. The previous k8s-client
   mock threw unconditionally from loadFromCluster(), which made the
   branch invisible to tests. loadFromCluster / loadFromDefault are
   now module-scope vi.fn() spies so the branch is asserted.

6. resolveSandboxName capital-N backcompat coverage: two new tests
   assert that the Name-casing dual-field read works against a mock
   watch event carrying `status.sandbox.Name` and that lowercase
   `name` wins when both are present.

7. initialFiles happy and failure paths: two new tests in sandbox.ts
   for create() -- one verifies string + Uint8Array encoding reaches
   the uploadFiles execute() calls with the right shell-level base64
   shapes, the other verifies that a mid-batch upload failure throws
   SANDBOX_CREATION_FAILED, calls sandbox.close() to tear down the
   tunnel, and then deletes the claim. Also adds coverage for the
   deleteAll empty-labels refusal path.

Test totals: 102 unit tests pass (was 86), 97 int tests still pass
against a real kind cluster.
johannhartmann added a commit to mayflower/agent-sandbox that referenced this pull request Apr 8, 2026
Ports the K8sAgentSandbox provider from
langchain-ai/deepagentsjs (feat/k8s-agent-sandbox branch) into this
repo as clients/typescript/langchain-agent-sandbox, mirroring the
Python clients/python/langchain-agent-sandbox package. Upgraded to
deepagents >=1.9 (the new V2 BackendProtocol) and integrated into
the repo's kind e2e suite. npm package name: langchain-agent-sandbox
(unscoped, matches the Python sibling).

## Package contents

clients/typescript/langchain-agent-sandbox/:
- src/sandbox.ts      K8sAgentSandbox extends BaseSandbox with
                      execute / uploadFiles / downloadFiles / id +
                      static factories fromUrl / create / fromExisting /
                      deleteAll. File operations (ls / read / readRaw /
                      grep / glob / write / edit) are inherited from
                      the V2 BaseSandbox defaults that delegate to
                      execute() with POSIX shell.
- src/http-client.ts  SandboxRouterClient — HTTP transport layer that
                      talks to the sandbox-router-svc with
                      X-Sandbox-ID / X-Sandbox-Namespace / X-Sandbox-Port
                      headers. Same wire protocol as the Python and
                      Go SDKs.
- src/k8s-client.ts   K8sClient — thin wrapper around
                      @kubernetes/client-node for SandboxClaim CRUD,
                      resolveSandboxName, waitForSandboxReady, Gateway
                      IP discovery, and label-selector-filtered listing.
- src/connection.ts   Three connection strategies: DirectConnectionStrategy,
                      GatewayConnectionStrategy, TunnelConnectionStrategy
                      (kubectl port-forward subprocess).
- src/types.ts        Connection configs, K8sAgentSandboxOptions,
                      K8sAgentSandboxCreateOptions (including
                      initialFiles), K8sAgentSandboxError class with
                      typed code union including COMMAND_TIMEOUT.
- src/index.ts        Public exports.
- src/*.test.ts       4 unit test files, 102 tests, fully mocked.
- src/sandbox.int.test.ts  Integration suite wired to
                           @langchain/sandbox-standard-tests (97
                           behavior tests covering sandbox lifecycle,
                           command execution, file operations, write,
                           read, edit, ls, grep, glob, initialFiles,
                           and integration workflows).
- package.json, tsconfig.json, tsdown.config.ts, vitest.config.ts,
  .gitignore, .npmignore, README.md, pnpm-lock.yaml.

examples/langchain-deepagentsjs/: minimal end-to-end example with
main.ts (Anthropic + deepagents + K8sAgentSandbox), package.json,
README.md, sandbox-template.yaml, .gitignore.

## Repo integration

- Makefile: new `test-langchainjs` target runs
  `pnpm install --frozen-lockfile && pnpm typecheck && pnpm test`.
- .github/workflows/test-langchainjs.yml: PR-time unit test CI on
  Node 20 + 22 matrix when clients/typescript/langchain-agent-sandbox
  changes. Matches the existing minimal-permissions pattern.
- dev/tools/test-e2e: setup_typescript_sdk installs the package if
  pnpm is present (hard fails on `pnpm install --frozen-lockfile`
  failure to prevent stale-node_modules silent passes);
  run_typescript_e2e_tests runs vitest --mode int with
  KUBECONFIG=bin/KUBECONFIG and outputs bin/e2e-typescript-junit.xml.
  Both functions short-circuit cleanly when pnpm, the package dir, or
  LANGCHAIN_SANDBOX_TEMPLATE is missing, so they never block the Go
  and Python suites from running.
- dev/ci/presubmits/test-e2e: copies the new junit artifact into
  $ARTIFACTS when present.
- test/e2e/README.md: documents the shared sandbox-router dependency
  for all three SDKs (Python, Go, TS) as a first-class deployment
  prerequisite, with the full build + IMAGE_PLACEHOLDER substitution
  + kubectl apply workflow.
- Root README.md: TypeScript SDK section after the Python SDK
  section.

## deepagents-js V2 protocol

The existing upstream code extended BaseSandbox (V1) and implemented
only the four abstract members (execute, uploadFiles, downloadFiles,
id). Against deepagents@^1.9.0 those four members satisfy the V2
SandboxBackendProtocolV2 contract unchanged, and the file operations
inherit from the V2 BaseSandbox defaults. No method rewrites were
required — only targeted hardening (see below).

## Post-port hardening

Eight bugs / gaps surfaced during the migration were fixed in this
same commit:

1. execute() COMMAND_TIMEOUT detection: the original port wrapped
   every fetch() rejection in http-client.ts as CONNECTION_FAILED,
   including the DOMException raised by AbortSignal.timeout. The new
   http-client.ts lets errors named TimeoutError / AbortError
   propagate without wrapping so execute()'s catch block can
   distinguish them and emit a typed COMMAND_TIMEOUT error.

2. K8sClient constructor: @kubernetes/client-node's loadFromCluster()
   does not throw outside a pod — it silently creates a config with
   server URL https://undefined:undefined, which crashes at request
   time. Fixed by checking process.env.KUBERNETES_SERVICE_HOST to
   select loadFromCluster() vs loadFromDefault() explicitly. The
   in-pod path still works unchanged.

3. resolveSandboxName: older controllers (pre upstream PR kubernetes-sigs#440)
   serialize the field as status.sandbox.Name (capital N) because
   the Go JSON tag was explicitly `json:"Name,omitempty"`. PR kubernetes-sigs#440
   renamed it to `json:"name,omitempty"`. The extract callback now
   reads both field names with lowercase precedence, mirroring the
   Python SDK's backcompat shim in upstream PR kubernetes-sigs#515.

4. create() initialFiles: the deepagents-js shared standard test
   suite passes `initialFiles: Record<string, string | Uint8Array>`
   in createSandbox options to pre-populate files. Added the field
   to K8sAgentSandboxCreateOptions and upload-after-initialize
   handling with full cleanup on failure — on any upload error,
   sandbox.close() is called first to tear down the tunnel subprocess
   (preventing a `kubectl port-forward` leak), then the claim is
   deleted.

5. listSandboxClaims + deleteAll label filtering: the original port
   accepted a labels argument but silently ignored it, deleting
   every claim in the namespace. listSandboxClaims now accepts an
   optional labels record that is validated (RFC-1123 Kubernetes
   label regex) and serialized into a labelSelector. Values
   containing "," or "=" are rejected at serialization time so a
   label like {owner: "alice,env=prod"} can't silently widen into a
   two-predicate selector. deleteAll also refuses an empty labels
   object unless the caller passes { confirmDeleteAll: true },
   eliminating the original "delete everything" footgun by default.

6. execute() timeout distinction: new COMMAND_TIMEOUT error code
   lets callers distinguish a per-command timeout from a generic
   CONNECTION_FAILED / COMMAND_FAILED. Verified end-to-end against
   a real kind cluster — `sleep 30` with defaultTimeout=3 terminates
   at exactly 3005ms with the right typed error.

7. int test env-var gating: the LANGCHAIN_SANDBOX_TEMPLATE /
   LANGCHAIN_NAMESPACE env var names match the Python langchain-
   agent-sandbox e2e test for symmetry, with the legacy
   SANDBOX_TEMPLATE / SANDBOX_NAMESPACE names kept as fallbacks.
   sandboxStandardTests({ skip: !template, ... }) uses the shared
   suite's built-in skip config so the run stays clean when the
   template is unset.

8. docs: the sandbox-router Deployment manifest at
   clients/python/agentic-sandbox-client/sandbox-router/sandbox_router.yaml
   has a literal `image: IMAGE_PLACEHOLDER` line that requires
   build + sed substitution before applying — applying it raw
   produces a CrashLoopBackOff. Both READMEs now document the full
   build + substitute + apply workflow and link to the router's own
   README for configuration details.

## Verification

- 102 unit tests pass (`pnpm test`)
- 97 integration tests pass against a real kind cluster
  (`LANGCHAIN_SANDBOX_TEMPLATE=df-standard
    LANGCHAIN_NAMESPACE=darkfactory pnpm test:int`) — covers the
  full deepagents-js shared standard suite (sandbox lifecycle,
  command execution, file operations, write, read, edit, ls, grep,
  glob, initialFiles, integration workflows) plus provider-specific
  lifecycle tests.
- pnpm typecheck clean
- pnpm build produces dual ESM + CJS output
- make test-langchainjs clean
- All 14 .ts files carry the Apache-2.0 / Kubernetes Authors header
- No `any` introduced; #private fields used throughout

## Source attribution

Ported from langchain-ai/deepagentsjs feat/k8s-agent-sandbox branch
under Apache-2.0; the upstream source was MIT-licensed. All files
carry the kubernetes-sigs Apache-2.0 header.
johannhartmann added a commit to mayflower/agent-sandbox that referenced this pull request Apr 17, 2026
Ports the K8sAgentSandbox provider from
langchain-ai/deepagentsjs (feat/k8s-agent-sandbox branch) into this
repo as clients/typescript/langchain-agent-sandbox, mirroring the
Python clients/python/langchain-agent-sandbox package. Upgraded to
deepagents >=1.9 (the new V2 BackendProtocol) and integrated into
the repo's kind e2e suite. npm package name: langchain-agent-sandbox
(unscoped, matches the Python sibling).

## Package contents

clients/typescript/langchain-agent-sandbox/:
- src/sandbox.ts      K8sAgentSandbox extends BaseSandbox with
                      execute / uploadFiles / downloadFiles / id +
                      static factories fromUrl / create / fromExisting /
                      deleteAll. File operations (ls / read / readRaw /
                      grep / glob / write / edit) are inherited from
                      the V2 BaseSandbox defaults that delegate to
                      execute() with POSIX shell.
- src/http-client.ts  SandboxRouterClient — HTTP transport layer that
                      talks to the sandbox-router-svc with
                      X-Sandbox-ID / X-Sandbox-Namespace / X-Sandbox-Port
                      headers. Same wire protocol as the Python and
                      Go SDKs.
- src/k8s-client.ts   K8sClient — thin wrapper around
                      @kubernetes/client-node for SandboxClaim CRUD,
                      resolveSandboxName, waitForSandboxReady, Gateway
                      IP discovery, and label-selector-filtered listing.
- src/connection.ts   Three connection strategies: DirectConnectionStrategy,
                      GatewayConnectionStrategy, TunnelConnectionStrategy
                      (kubectl port-forward subprocess).
- src/types.ts        Connection configs, K8sAgentSandboxOptions,
                      K8sAgentSandboxCreateOptions (including
                      initialFiles), K8sAgentSandboxError class with
                      typed code union including COMMAND_TIMEOUT.
- src/index.ts        Public exports.
- src/*.test.ts       4 unit test files, 102 tests, fully mocked.
- src/sandbox.int.test.ts  Integration suite wired to
                           @langchain/sandbox-standard-tests (97
                           behavior tests covering sandbox lifecycle,
                           command execution, file operations, write,
                           read, edit, ls, grep, glob, initialFiles,
                           and integration workflows).
- package.json, tsconfig.json, tsdown.config.ts, vitest.config.ts,
  .gitignore, .npmignore, README.md, pnpm-lock.yaml.

examples/langchain-deepagentsjs/: minimal end-to-end example with
main.ts (Anthropic + deepagents + K8sAgentSandbox), package.json,
README.md, sandbox-template.yaml, .gitignore.

## Repo integration

- Makefile: new `test-langchainjs` target runs
  `pnpm install --frozen-lockfile && pnpm typecheck && pnpm test`.
- .github/workflows/test-langchainjs.yml: PR-time unit test CI on
  Node 20 + 22 matrix when clients/typescript/langchain-agent-sandbox
  changes. Matches the existing minimal-permissions pattern.
- dev/tools/test-e2e: setup_typescript_sdk installs the package if
  pnpm is present (hard fails on `pnpm install --frozen-lockfile`
  failure to prevent stale-node_modules silent passes);
  run_typescript_e2e_tests runs vitest --mode int with
  KUBECONFIG=bin/KUBECONFIG and outputs bin/e2e-typescript-junit.xml.
  Both functions short-circuit cleanly when pnpm, the package dir, or
  LANGCHAIN_SANDBOX_TEMPLATE is missing, so they never block the Go
  and Python suites from running.
- dev/ci/presubmits/test-e2e: copies the new junit artifact into
  $ARTIFACTS when present.
- test/e2e/README.md: documents the shared sandbox-router dependency
  for all three SDKs (Python, Go, TS) as a first-class deployment
  prerequisite, with the full build + IMAGE_PLACEHOLDER substitution
  + kubectl apply workflow.
- Root README.md: TypeScript SDK section after the Python SDK
  section.

## deepagents-js V2 protocol

The existing upstream code extended BaseSandbox (V1) and implemented
only the four abstract members (execute, uploadFiles, downloadFiles,
id). Against deepagents@^1.9.0 those four members satisfy the V2
SandboxBackendProtocolV2 contract unchanged, and the file operations
inherit from the V2 BaseSandbox defaults. No method rewrites were
required — only targeted hardening (see below).

## Post-port hardening

Eight bugs / gaps surfaced during the migration were fixed in this
same commit:

1. execute() COMMAND_TIMEOUT detection: the original port wrapped
   every fetch() rejection in http-client.ts as CONNECTION_FAILED,
   including the DOMException raised by AbortSignal.timeout. The new
   http-client.ts lets errors named TimeoutError / AbortError
   propagate without wrapping so execute()'s catch block can
   distinguish them and emit a typed COMMAND_TIMEOUT error.

2. K8sClient constructor: @kubernetes/client-node's loadFromCluster()
   does not throw outside a pod — it silently creates a config with
   server URL https://undefined:undefined, which crashes at request
   time. Fixed by checking process.env.KUBERNETES_SERVICE_HOST to
   select loadFromCluster() vs loadFromDefault() explicitly. The
   in-pod path still works unchanged.

3. resolveSandboxName: older controllers (pre upstream PR kubernetes-sigs#440)
   serialize the field as status.sandbox.Name (capital N) because
   the Go JSON tag was explicitly `json:"Name,omitempty"`. PR kubernetes-sigs#440
   renamed it to `json:"name,omitempty"`. The extract callback now
   reads both field names with lowercase precedence, mirroring the
   Python SDK's backcompat shim in upstream PR kubernetes-sigs#515.

4. create() initialFiles: the deepagents-js shared standard test
   suite passes `initialFiles: Record<string, string | Uint8Array>`
   in createSandbox options to pre-populate files. Added the field
   to K8sAgentSandboxCreateOptions and upload-after-initialize
   handling with full cleanup on failure — on any upload error,
   sandbox.close() is called first to tear down the tunnel subprocess
   (preventing a `kubectl port-forward` leak), then the claim is
   deleted.

5. listSandboxClaims + deleteAll label filtering: the original port
   accepted a labels argument but silently ignored it, deleting
   every claim in the namespace. listSandboxClaims now accepts an
   optional labels record that is validated (RFC-1123 Kubernetes
   label regex) and serialized into a labelSelector. Values
   containing "," or "=" are rejected at serialization time so a
   label like {owner: "alice,env=prod"} can't silently widen into a
   two-predicate selector. deleteAll also refuses an empty labels
   object unless the caller passes { confirmDeleteAll: true },
   eliminating the original "delete everything" footgun by default.

6. execute() timeout distinction: new COMMAND_TIMEOUT error code
   lets callers distinguish a per-command timeout from a generic
   CONNECTION_FAILED / COMMAND_FAILED. Verified end-to-end against
   a real kind cluster — `sleep 30` with defaultTimeout=3 terminates
   at exactly 3005ms with the right typed error.

7. int test env-var gating: the LANGCHAIN_SANDBOX_TEMPLATE /
   LANGCHAIN_NAMESPACE env var names match the Python langchain-
   agent-sandbox e2e test for symmetry, with the legacy
   SANDBOX_TEMPLATE / SANDBOX_NAMESPACE names kept as fallbacks.
   sandboxStandardTests({ skip: !template, ... }) uses the shared
   suite's built-in skip config so the run stays clean when the
   template is unset.

8. docs: the sandbox-router Deployment manifest at
   clients/python/agentic-sandbox-client/sandbox-router/sandbox_router.yaml
   has a literal `image: IMAGE_PLACEHOLDER` line that requires
   build + sed substitution before applying — applying it raw
   produces a CrashLoopBackOff. Both READMEs now document the full
   build + substitute + apply workflow and link to the router's own
   README for configuration details.

## Verification

- 102 unit tests pass (`pnpm test`)
- 97 integration tests pass against a real kind cluster
  (`LANGCHAIN_SANDBOX_TEMPLATE=df-standard
    LANGCHAIN_NAMESPACE=darkfactory pnpm test:int`) — covers the
  full deepagents-js shared standard suite (sandbox lifecycle,
  command execution, file operations, write, read, edit, ls, grep,
  glob, initialFiles, integration workflows) plus provider-specific
  lifecycle tests.
- pnpm typecheck clean
- pnpm build produces dual ESM + CJS output
- make test-langchainjs clean
- All 14 .ts files carry the Apache-2.0 / Kubernetes Authors header
- No `any` introduced; #private fields used throughout

## Source attribution

Ported from langchain-ai/deepagentsjs feat/k8s-agent-sandbox branch
under Apache-2.0; the upstream source was MIT-licensed. All files
carry the kubernetes-sigs Apache-2.0 header.
johannhartmann added a commit to mayflower/agent-sandbox that referenced this pull request Apr 18, 2026
Ports the K8sAgentSandbox provider from
langchain-ai/deepagentsjs (feat/k8s-agent-sandbox branch) into this
repo as clients/typescript/langchain-agent-sandbox, mirroring the
Python clients/python/langchain-agent-sandbox package. Upgraded to
deepagents >=1.9 (the new V2 BackendProtocol) and integrated into
the repo's kind e2e suite. npm package name: langchain-agent-sandbox
(unscoped, matches the Python sibling).

## Package contents

clients/typescript/langchain-agent-sandbox/:
- src/sandbox.ts      K8sAgentSandbox extends BaseSandbox with
                      execute / uploadFiles / downloadFiles / id +
                      static factories fromUrl / create / fromExisting /
                      deleteAll. File operations (ls / read / readRaw /
                      grep / glob / write / edit) are inherited from
                      the V2 BaseSandbox defaults that delegate to
                      execute() with POSIX shell.
- src/http-client.ts  SandboxRouterClient — HTTP transport layer that
                      talks to the sandbox-router-svc with
                      X-Sandbox-ID / X-Sandbox-Namespace / X-Sandbox-Port
                      headers. Same wire protocol as the Python and
                      Go SDKs.
- src/k8s-client.ts   K8sClient — thin wrapper around
                      @kubernetes/client-node for SandboxClaim CRUD,
                      resolveSandboxName, waitForSandboxReady, Gateway
                      IP discovery, and label-selector-filtered listing.
- src/connection.ts   Three connection strategies: DirectConnectionStrategy,
                      GatewayConnectionStrategy, TunnelConnectionStrategy
                      (kubectl port-forward subprocess).
- src/types.ts        Connection configs, K8sAgentSandboxOptions,
                      K8sAgentSandboxCreateOptions (including
                      initialFiles), K8sAgentSandboxError class with
                      typed code union including COMMAND_TIMEOUT.
- src/index.ts        Public exports.
- src/*.test.ts       4 unit test files, 102 tests, fully mocked.
- src/sandbox.int.test.ts  Integration suite wired to
                           @langchain/sandbox-standard-tests (97
                           behavior tests covering sandbox lifecycle,
                           command execution, file operations, write,
                           read, edit, ls, grep, glob, initialFiles,
                           and integration workflows).
- package.json, tsconfig.json, tsdown.config.ts, vitest.config.ts,
  .gitignore, .npmignore, README.md, pnpm-lock.yaml.

examples/langchain-deepagentsjs/: minimal end-to-end example with
main.ts (Anthropic + deepagents + K8sAgentSandbox), package.json,
README.md, sandbox-template.yaml, .gitignore.

## Repo integration

- Makefile: new `test-langchainjs` target runs
  `pnpm install --frozen-lockfile && pnpm typecheck && pnpm test`.
- .github/workflows/test-langchainjs.yml: PR-time unit test CI on
  Node 20 + 22 matrix when clients/typescript/langchain-agent-sandbox
  changes. Matches the existing minimal-permissions pattern.
- dev/tools/test-e2e: setup_typescript_sdk installs the package if
  pnpm is present (hard fails on `pnpm install --frozen-lockfile`
  failure to prevent stale-node_modules silent passes);
  run_typescript_e2e_tests runs vitest --mode int with
  KUBECONFIG=bin/KUBECONFIG and outputs bin/e2e-typescript-junit.xml.
  Both functions short-circuit cleanly when pnpm, the package dir, or
  LANGCHAIN_SANDBOX_TEMPLATE is missing, so they never block the Go
  and Python suites from running.
- dev/ci/presubmits/test-e2e: copies the new junit artifact into
  $ARTIFACTS when present.
- test/e2e/README.md: documents the shared sandbox-router dependency
  for all three SDKs (Python, Go, TS) as a first-class deployment
  prerequisite, with the full build + IMAGE_PLACEHOLDER substitution
  + kubectl apply workflow.
- Root README.md: TypeScript SDK section after the Python SDK
  section.

## deepagents-js V2 protocol

The existing upstream code extended BaseSandbox (V1) and implemented
only the four abstract members (execute, uploadFiles, downloadFiles,
id). Against deepagents@^1.9.0 those four members satisfy the V2
SandboxBackendProtocolV2 contract unchanged, and the file operations
inherit from the V2 BaseSandbox defaults. No method rewrites were
required — only targeted hardening (see below).

## Post-port hardening

Eight bugs / gaps surfaced during the migration were fixed in this
same commit:

1. execute() COMMAND_TIMEOUT detection: the original port wrapped
   every fetch() rejection in http-client.ts as CONNECTION_FAILED,
   including the DOMException raised by AbortSignal.timeout. The new
   http-client.ts lets errors named TimeoutError / AbortError
   propagate without wrapping so execute()'s catch block can
   distinguish them and emit a typed COMMAND_TIMEOUT error.

2. K8sClient constructor: @kubernetes/client-node's loadFromCluster()
   does not throw outside a pod — it silently creates a config with
   server URL https://undefined:undefined, which crashes at request
   time. Fixed by checking process.env.KUBERNETES_SERVICE_HOST to
   select loadFromCluster() vs loadFromDefault() explicitly. The
   in-pod path still works unchanged.

3. resolveSandboxName: older controllers (pre upstream PR kubernetes-sigs#440)
   serialize the field as status.sandbox.Name (capital N) because
   the Go JSON tag was explicitly `json:"Name,omitempty"`. PR kubernetes-sigs#440
   renamed it to `json:"name,omitempty"`. The extract callback now
   reads both field names with lowercase precedence, mirroring the
   Python SDK's backcompat shim in upstream PR kubernetes-sigs#515.

4. create() initialFiles: the deepagents-js shared standard test
   suite passes `initialFiles: Record<string, string | Uint8Array>`
   in createSandbox options to pre-populate files. Added the field
   to K8sAgentSandboxCreateOptions and upload-after-initialize
   handling with full cleanup on failure — on any upload error,
   sandbox.close() is called first to tear down the tunnel subprocess
   (preventing a `kubectl port-forward` leak), then the claim is
   deleted.

5. listSandboxClaims + deleteAll label filtering: the original port
   accepted a labels argument but silently ignored it, deleting
   every claim in the namespace. listSandboxClaims now accepts an
   optional labels record that is validated (RFC-1123 Kubernetes
   label regex) and serialized into a labelSelector. Values
   containing "," or "=" are rejected at serialization time so a
   label like {owner: "alice,env=prod"} can't silently widen into a
   two-predicate selector. deleteAll also refuses an empty labels
   object unless the caller passes { confirmDeleteAll: true },
   eliminating the original "delete everything" footgun by default.

6. execute() timeout distinction: new COMMAND_TIMEOUT error code
   lets callers distinguish a per-command timeout from a generic
   CONNECTION_FAILED / COMMAND_FAILED. Verified end-to-end against
   a real kind cluster — `sleep 30` with defaultTimeout=3 terminates
   at exactly 3005ms with the right typed error.

7. int test env-var gating: the LANGCHAIN_SANDBOX_TEMPLATE /
   LANGCHAIN_NAMESPACE env var names match the Python langchain-
   agent-sandbox e2e test for symmetry, with the legacy
   SANDBOX_TEMPLATE / SANDBOX_NAMESPACE names kept as fallbacks.
   sandboxStandardTests({ skip: !template, ... }) uses the shared
   suite's built-in skip config so the run stays clean when the
   template is unset.

8. docs: the sandbox-router Deployment manifest at
   clients/python/agentic-sandbox-client/sandbox-router/sandbox_router.yaml
   has a literal `image: IMAGE_PLACEHOLDER` line that requires
   build + sed substitution before applying — applying it raw
   produces a CrashLoopBackOff. Both READMEs now document the full
   build + substitute + apply workflow and link to the router's own
   README for configuration details.

## Verification

- 102 unit tests pass (`pnpm test`)
- 97 integration tests pass against a real kind cluster
  (`LANGCHAIN_SANDBOX_TEMPLATE=df-standard
    LANGCHAIN_NAMESPACE=darkfactory pnpm test:int`) — covers the
  full deepagents-js shared standard suite (sandbox lifecycle,
  command execution, file operations, write, read, edit, ls, grep,
  glob, initialFiles, integration workflows) plus provider-specific
  lifecycle tests.
- pnpm typecheck clean
- pnpm build produces dual ESM + CJS output
- make test-langchainjs clean
- All 14 .ts files carry the Apache-2.0 / Kubernetes Authors header
- No `any` introduced; #private fields used throughout

## Source attribution

Ported from langchain-ai/deepagentsjs feat/k8s-agent-sandbox branch
under Apache-2.0; the upstream source was MIT-licensed. All files
carry the kubernetes-sigs Apache-2.0 header.
johannhartmann added a commit to mayflower/agent-sandbox that referenced this pull request Apr 24, 2026
Ports the K8sAgentSandbox provider from
langchain-ai/deepagentsjs (feat/k8s-agent-sandbox branch) into this
repo as clients/typescript/langchain-agent-sandbox, mirroring the
Python clients/python/langchain-agent-sandbox package. Upgraded to
deepagents >=1.9 (the new V2 BackendProtocol) and integrated into
the repo's kind e2e suite. npm package name: langchain-agent-sandbox
(unscoped, matches the Python sibling).

## Package contents

clients/typescript/langchain-agent-sandbox/:
- src/sandbox.ts      K8sAgentSandbox extends BaseSandbox with
                      execute / uploadFiles / downloadFiles / id +
                      static factories fromUrl / create / fromExisting /
                      deleteAll. File operations (ls / read / readRaw /
                      grep / glob / write / edit) are inherited from
                      the V2 BaseSandbox defaults that delegate to
                      execute() with POSIX shell.
- src/http-client.ts  SandboxRouterClient — HTTP transport layer that
                      talks to the sandbox-router-svc with
                      X-Sandbox-ID / X-Sandbox-Namespace / X-Sandbox-Port
                      headers. Same wire protocol as the Python and
                      Go SDKs.
- src/k8s-client.ts   K8sClient — thin wrapper around
                      @kubernetes/client-node for SandboxClaim CRUD,
                      resolveSandboxName, waitForSandboxReady, Gateway
                      IP discovery, and label-selector-filtered listing.
- src/connection.ts   Three connection strategies: DirectConnectionStrategy,
                      GatewayConnectionStrategy, TunnelConnectionStrategy
                      (kubectl port-forward subprocess).
- src/types.ts        Connection configs, K8sAgentSandboxOptions,
                      K8sAgentSandboxCreateOptions (including
                      initialFiles), K8sAgentSandboxError class with
                      typed code union including COMMAND_TIMEOUT.
- src/index.ts        Public exports.
- src/*.test.ts       4 unit test files, 102 tests, fully mocked.
- src/sandbox.int.test.ts  Integration suite wired to
                           @langchain/sandbox-standard-tests (97
                           behavior tests covering sandbox lifecycle,
                           command execution, file operations, write,
                           read, edit, ls, grep, glob, initialFiles,
                           and integration workflows).
- package.json, tsconfig.json, tsdown.config.ts, vitest.config.ts,
  .gitignore, .npmignore, README.md, pnpm-lock.yaml.

examples/langchain-deepagentsjs/: minimal end-to-end example with
main.ts (Anthropic + deepagents + K8sAgentSandbox), package.json,
README.md, sandbox-template.yaml, .gitignore.

## Repo integration

- Makefile: new `test-langchainjs` target runs
  `pnpm install --frozen-lockfile && pnpm typecheck && pnpm test`.
- .github/workflows/test-langchainjs.yml: PR-time unit test CI on
  Node 20 + 22 matrix when clients/typescript/langchain-agent-sandbox
  changes. Matches the existing minimal-permissions pattern.
- dev/tools/test-e2e: setup_typescript_sdk installs the package if
  pnpm is present (hard fails on `pnpm install --frozen-lockfile`
  failure to prevent stale-node_modules silent passes);
  run_typescript_e2e_tests runs vitest --mode int with
  KUBECONFIG=bin/KUBECONFIG and outputs bin/e2e-typescript-junit.xml.
  Both functions short-circuit cleanly when pnpm, the package dir, or
  LANGCHAIN_SANDBOX_TEMPLATE is missing, so they never block the Go
  and Python suites from running.
- dev/ci/presubmits/test-e2e: copies the new junit artifact into
  $ARTIFACTS when present.
- test/e2e/README.md: documents the shared sandbox-router dependency
  for all three SDKs (Python, Go, TS) as a first-class deployment
  prerequisite, with the full build + IMAGE_PLACEHOLDER substitution
  + kubectl apply workflow.
- Root README.md: TypeScript SDK section after the Python SDK
  section.

## deepagents-js V2 protocol

The existing upstream code extended BaseSandbox (V1) and implemented
only the four abstract members (execute, uploadFiles, downloadFiles,
id). Against deepagents@^1.9.0 those four members satisfy the V2
SandboxBackendProtocolV2 contract unchanged, and the file operations
inherit from the V2 BaseSandbox defaults. No method rewrites were
required — only targeted hardening (see below).

## Post-port hardening

Eight bugs / gaps surfaced during the migration were fixed in this
same commit:

1. execute() COMMAND_TIMEOUT detection: the original port wrapped
   every fetch() rejection in http-client.ts as CONNECTION_FAILED,
   including the DOMException raised by AbortSignal.timeout. The new
   http-client.ts lets errors named TimeoutError / AbortError
   propagate without wrapping so execute()'s catch block can
   distinguish them and emit a typed COMMAND_TIMEOUT error.

2. K8sClient constructor: @kubernetes/client-node's loadFromCluster()
   does not throw outside a pod — it silently creates a config with
   server URL https://undefined:undefined, which crashes at request
   time. Fixed by checking process.env.KUBERNETES_SERVICE_HOST to
   select loadFromCluster() vs loadFromDefault() explicitly. The
   in-pod path still works unchanged.

3. resolveSandboxName: older controllers (pre upstream PR kubernetes-sigs#440)
   serialize the field as status.sandbox.Name (capital N) because
   the Go JSON tag was explicitly `json:"Name,omitempty"`. PR kubernetes-sigs#440
   renamed it to `json:"name,omitempty"`. The extract callback now
   reads both field names with lowercase precedence, mirroring the
   Python SDK's backcompat shim in upstream PR kubernetes-sigs#515.

4. create() initialFiles: the deepagents-js shared standard test
   suite passes `initialFiles: Record<string, string | Uint8Array>`
   in createSandbox options to pre-populate files. Added the field
   to K8sAgentSandboxCreateOptions and upload-after-initialize
   handling with full cleanup on failure — on any upload error,
   sandbox.close() is called first to tear down the tunnel subprocess
   (preventing a `kubectl port-forward` leak), then the claim is
   deleted.

5. listSandboxClaims + deleteAll label filtering: the original port
   accepted a labels argument but silently ignored it, deleting
   every claim in the namespace. listSandboxClaims now accepts an
   optional labels record that is validated (RFC-1123 Kubernetes
   label regex) and serialized into a labelSelector. Values
   containing "," or "=" are rejected at serialization time so a
   label like {owner: "alice,env=prod"} can't silently widen into a
   two-predicate selector. deleteAll also refuses an empty labels
   object unless the caller passes { confirmDeleteAll: true },
   eliminating the original "delete everything" footgun by default.

6. execute() timeout distinction: new COMMAND_TIMEOUT error code
   lets callers distinguish a per-command timeout from a generic
   CONNECTION_FAILED / COMMAND_FAILED. Verified end-to-end against
   a real kind cluster — `sleep 30` with defaultTimeout=3 terminates
   at exactly 3005ms with the right typed error.

7. int test env-var gating: the LANGCHAIN_SANDBOX_TEMPLATE /
   LANGCHAIN_NAMESPACE env var names match the Python langchain-
   agent-sandbox e2e test for symmetry, with the legacy
   SANDBOX_TEMPLATE / SANDBOX_NAMESPACE names kept as fallbacks.
   sandboxStandardTests({ skip: !template, ... }) uses the shared
   suite's built-in skip config so the run stays clean when the
   template is unset.

8. docs: the sandbox-router Deployment manifest at
   clients/python/agentic-sandbox-client/sandbox-router/sandbox_router.yaml
   has a literal `image: IMAGE_PLACEHOLDER` line that requires
   build + sed substitution before applying — applying it raw
   produces a CrashLoopBackOff. Both READMEs now document the full
   build + substitute + apply workflow and link to the router's own
   README for configuration details.

## Verification

- 102 unit tests pass (`pnpm test`)
- 97 integration tests pass against a real kind cluster
  (`LANGCHAIN_SANDBOX_TEMPLATE=df-standard
    LANGCHAIN_NAMESPACE=darkfactory pnpm test:int`) — covers the
  full deepagents-js shared standard suite (sandbox lifecycle,
  command execution, file operations, write, read, edit, ls, grep,
  glob, initialFiles, integration workflows) plus provider-specific
  lifecycle tests.
- pnpm typecheck clean
- pnpm build produces dual ESM + CJS output
- make test-langchainjs clean
- All 14 .ts files carry the Apache-2.0 / Kubernetes Authors header
- No `any` introduced; #private fields used throughout

## Source attribution

Ported from langchain-ai/deepagentsjs feat/k8s-agent-sandbox branch
under Apache-2.0; the upstream source was MIT-licensed. All files
carry the kubernetes-sigs Apache-2.0 header.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. release-note-action-required Denotes a PR that introduces potentially breaking changes that require user action. size/M Denotes a PR that changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants