Skip to content

feat(engine): self-hosted Engine and sandbox runtime support - #889

Merged
Bagatur (baskaryan) merged 77 commits into
v16-stablefrom
feat/engine-self-hosted-v16-sandboxes
Jul 31, 2026
Merged

Bagatur (baskaryan) merged 77 commits into
v16-stablefrom
feat/engine-self-hosted-v16-sandboxes

Conversation

@baskaryan

Copy link
Copy Markdown
Contributor

Brings self-hosted Engine to the v16 train: the sandbox runtime it depends on, and the chart wiring to turn Engine on. 67 commits, +3826/-511 across 58 chart files.

Draft while #881 is settled — it targets this branch and would otherwise be stranded once this merges.

What lands

Sandbox runtime (config.sandboxes.enabled, Daniel's work). New templates/sandboxes/: the sandbox-host StatefulSet and RBAC, the JuiceFS CSI driver (controller, node DaemonSet, CSIDriver, RBAC, configmap), and the proxy CA. Off by default; when on, it installs cluster-scoped CSI resources, so only one sandbox-enabled release should manage them per cluster.

Engine (engine.enabled). Off by default. Requires the combined langsmith-insights-engine image, which serves both the insights and engine graphs — published publicly by the self-hosted release workflow.

Insights and Engine now share one deployment. Scaling, database and resource settings moved from insights.* to a new engineInsightsAgent.* block, and images.{insightsAgentImage,insightsEngineImage} became images.engineInsightsAgentImage. insights.enabled and insights.encryptionKey stay where they are. Every moved key has a migration fail naming its replacement, so an operator carrying the old values gets told rather than silently ignored.

Upgrade notes

  • Insights-only installs are unaffected in behaviour, but must rename the moved values above.
  • engine.enabled additionally requires config.sandboxes.enabled, engine.encryptionKey, engine.intelligenceBaseUrl, engine.sandboxTenantId, and a config.hostname a sandbox can actually reach — validation rejects loopback and in-cluster addresses, since that host is allow-listed for sandbox egress and receives injected credentials.
  • engine.cliEndpoint no longer exists: the agent derives the in-sandbox CLI URL from LANGCHAIN_PLATFORM_ENDPOINT. Needs an agent image with langchainplus #32399 — 0.16.27rc1 and later.

Test Plan

  • helm unittest charts/langsmith — 248/248 across 19 suites
  • helm lint clean
  • Renders with engine + sandboxes enabled, and with engine off (insights-only) unchanged
  • Rendered against self-hosted-ci's real values: no LANGSMITH_CLI_ENDPOINT, LANGCHAIN_PLATFORM_ENDPOINT=https://eks.smith.langchain.dev/api
  • v16-stable merged in (Merge v16-stable into feat/engine-self-hosted-v16-sandboxes #888) — branch is 0 behind
  • Not yet exercised on a live cluster; self-hosted-ci deploys from this branch, so the next run is the first end-to-end Engine test

Before merging

  1. Settle refactor(engine): drop the per-service JWT secrets from the co-host #881 (drops the per-service JWT secrets) — its blocker cleared, 0.16.27rc1 contains langchainplus #31926
  2. Chart bump on merge: appVersion → 0.16.27rc1, version → 0.16.0-rc.23, image tags, regenerate README

…ndboxes-helm

# Conflicts:
#	charts/langsmith/Chart.yaml
#	charts/langsmith/README.md
…ndboxes-helm

# Conflicts:
#	charts/langsmith/Chart.yaml
Wires the co-hosted Insights+Engine LangGraph deployment for self-hosted,
gated behind engine.enabled (default false, so Insights-only installs are
unchanged).

- engine values block (enabled / intelligenceBaseUrl / encryptionKey) and
  engine_encryption_key in the chart secret.
- Engine pods get ENGINE_INTELLIGENCE_BASE_URL, ISSUES_AGENT_ENCRYPTION_KEY,
  and the per-service X-Service-Key JWT verifier secrets.
- smith-go (platform-backend + ingest-queue) gets FORGE_AGENT_LANGGRAPH_URL,
  SMITH_GO_SERVICE_JWT_SECRET, ISSUES_AGENT_ENCRYPTION_KEY, and
  FORGE_AGENT_ASSISTANT_ID=engine. The combined langgraph.json names the graph
  `engine`; combined_auth rejects smith-go's default `smith_issues_agent` (403).
- Insights image selects the combined langsmith-insights-engine image when
  engine.enabled, else standalone langsmith-clio.
- smith-backend (backend + queue) gets CLIO_ASSISTANT_ID=insights when
  engine.enabled: the combined image names the CLIO graph `insights`, not the
  default `agent`.
- validate.yaml: require insights.enabled, encryptionKey, and
  intelligenceBaseUrl when engine.enabled.

Based on v16-stable rather than main: this ships in v16, and main's
agent-bootstrap removal touches the same three files, so a cherry-pick back
would conflict.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Engine and Insights run as one image on one deployment, so their deployment
config now lives in a single block instead of two sibling product blocks.

- engine / insights stay as top-level product toggles (enabled, encryptionKey;
  engine also carries intelligenceBaseUrl). Both encryption keys stay per
  product: they are different secrets for different products.
- New engineInsightsAgent block holds the shared deployment config moved from
  insights: namePrefix, apiServer, queue, postgres, redis. namePrefix keeps its
  "standalone-insights" value so no Kubernetes resource is renamed and the
  postgres/redis StatefulSets keep their volumes.
- images.insightsAgentImage + images.insightsEngineImage collapse into
  images.engineInsightsAgentImage. validate.yaml requires the combined
  insights-engine image when engine.enabled.
- The shared deployment now renders when either product is on, so Engine can
  run with Insights disabled. Replaces the earlier "engine requires
  insights.enabled" guard.
- validate.yaml hard-fails on the moved keys rather than silently ignoring
  them, so an upgrade cannot quietly drop a customized database or resource
  setting. Agent-feature validation now checks encryptionKey per product and
  postgres/redis per deployment.

Rendered output for an Insights-only install is byte-identical to before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both keys are shared with a counterpart service (engine with smith-go,
insights with smith-backend), so rotating either was a flash cut: payloads
encrypted just before the swap fail to decrypt after it.

Add encryptionKeyPrevious for both products, written to the
engine_encryption_key_previous / insights_encryption_key_previous secret
fields and exposed as ISSUES_AGENT_ENCRYPTION_KEY_PREVIOUS /
CLIO_ENCRYPTION_KEY_PREVIOUS. Both are optional: true, since a previous key
is absent in the common case and the chart cannot see inside an existing
secret when config.existingSecretName is set.

smith-clio already accepted CLIO_ENCRYPTION_KEY_PREVIOUS but the chart never
wired it, so rotation was unreachable from Helm for either product. The agent
side for engine is langchainplus worktree-engine-key-rotation.

Also adds the engine.* rows missing from the README values table.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
api_key_salt is the API-key hashing and service-JWT signing salt, not an API
key. It was only set because smith-go rejected an empty
FORGE_AGENT_LANGSMITH_API_KEY even when authenticating with a service-key
JWT, which is the only mode self-hosted uses. Had the API-key fallback ever
been taken, the salt would have gone out as x-api-key.

smith-go now requires the key only in API-key mode (langchainplus
worktree-engine-service-key-auth), so drop the variable. Engine dispatch
authenticates with the x-service-key JWT signed from SMITH_GO_SERVICE_JWT_SECRET.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…m' into feat/engine-self-hosted-v16-sandboxes

# Conflicts:
#	charts/langsmith/README.md
#	charts/langsmith/templates/ingest-queue/deployment.yaml
#	charts/langsmith/templates/platform-backend/deployment.yaml
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(engine): set auth prerequisites on the engine validation tests

Both engine cases asserted on the engine failure message but tripped the
earlier apiKeySalt and auth guards first, so they passed for the wrong reason
and never reached the assertion under test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(engine): wire the sandbox tenant id for self-hosted Engine

The Engine signs its sandbox calls with a service key that must carry a
tenant_id claim, read from ISSUES_AGENT_SANDBOX_TENANT_ID. The chart set no
such value, so a self-hosted Engine raised at sandbox-create time.

Engine sandboxes live in one designated workspace rather than the run's own
tenant: a sandbox in the customer's workspace would consume their sandbox
quota, be visible and stoppable by them, and hold a GitHub App token for the
repo under analysis. SaaS uses a LangChain-internal workspace; self-hosted has
no vendor-owned one and there is no system-tenant primitive yet, so the
operator designates one. The values comment spells out the consequences so
that choice is informed.

Required only when sandboxes are enabled, and validated at template time —
otherwise the failure surfaces as a runtime error on the first Engine run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(engine): require sandboxes, and the tenant, whenever engine is enabled

The tenant id was only required when config.sandboxes.enabled, but the agent
raises whenever X_SERVICE_AUTH_JWT_SECRET is set -- which is on every pod,
independent of that flag. So engine.enabled with sandboxes disabled passed
validation and then failed at the first sandbox call.

Engine cannot run without sandboxes at all: every run shells out to the
langsmith CLI inside one. Make that a guard of its own, which lets the tenant
guard key on engine.enabled alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(engine): point the Engine at its sandbox snapshot and base image

Engine runs boot from a named snapshot the Engine builds itself on first use,
so a self-hosted install needs no deploy-time snapshot build — that build wants
egress to the Wolfi, astral.sh, npm and GitHub hosts, which a customer cluster
may not have.

The image goes through langsmith.image, so images.registry rewrites it for a
mirrored registry like every other chart image. Both values default, so this
adds no required configuration.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Revert "feat(engine): point the Engine at its sandbox snapshot and base image"

This reverts commit c36b767.

* test(engine): drop the sandboxes-disabled tenant assertion

Reverting the snapshot wiring restored this case, but the tenant env var is no
longer gated on config.sandboxes.enabled, and engine.enabled now requires
sandboxes — so the combination it asserts cannot render.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both defaulted to SaaS, so a self-hosted Engine created sandboxes against
api.smith.langchain.com and its in-sandbox CLI had nowhere reachable to call.

LANGSMITH_SANDBOX_ENDPOINT is derived in-cluster like GO_ENDPOINT -- that hop
is Engine pod to platform-backend, so the service URL is fine.

engine.cliEndpoint has to be supplied: the sandbox runs the langsmith CLI
against it, so it must be externally routable, and setting it is also what
adds the host to the sandbox egress allow-list and to the proxy's
credential-injection rules. The chart cannot infer that from ingress, base
path and custom domains, so it is required when engine.enabled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(engine): derive the sandbox CLI endpoint from config.hostname

engine.cliEndpoint was required whenever engine.enabled was true, which
broke self-hosted-ci. The value is almost always <config.hostname>/api,
so default it to the existing sandboxes.platformEndpoint helper and keep
engine.cliEndpoint as an override for hosts config.hostname doesn't name.

Validation now rejects only what cannot be inferred: no hostname at all,
or a hostname no sandbox could reach (loopback, in-cluster .svc). Those
still need an explicit override, since the sandbox proxy allow-lists this
host and injects LangSmith credentials for it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(engine): drop engine.cliEndpoint from the chart (#883)

langchainplus #32399 removes LANGSMITH_CLI_ENDPOINT from the agent, which now
derives the in-sandbox CLI URL from LANGCHAIN_PLATFORM_ENDPOINT — already on
the pod via the shared config map whenever sandboxes are enabled, which Engine
requires. So the chart has nothing left to set.

Removes the values key, the env var, and the helper. The validation stays but
keys off config.hostname alone, since that is what the platform endpoint is
derived from and an unreachable one still has to be caught.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(engine): keep the README diff to the rows this branch changes

helm-docs reordered ~191 identical rows, because nothing in the repo pins a
version and the last person to regenerate used a different one. Restored the
base file and applied only the engine.sandboxTenantId row by hand, so the diff
shows what this branch actually changed.

Leaves the sandboxes keys Daniel's merge never documented; that regeneration
belongs with a version pin, not here.

Also renames a test whose title still mentioned the removed cli endpoint.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
The branch forked at #591 and was 18 commits behind, which has to be
reconciled before it can target v16-stable.

Conflicts:
- images: keep the engineInsightsAgentImage rename, take v16-stable's tags
- insights.extraEnv: keep the engine block, add the IAM auth provider vars
- validate.yaml: keep both migration guards; the IAM provider check reads
  engineInsightsAgent, where postgres/redis moved to
- agent_iam_provider_test.yaml: same, so its fixtures resolve
- README.md: regenerated with helm-docs

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bagatur (baskaryan) added a commit that referenced this pull request Jul 31, 2026
…890)

langchainplus #32383 published the 0.16.27rc1 images. Clusters that track the
published stable chart, such as aks.smith.langchain.dev, only pick up the
cherry-picks in that release once the chart is cut.

Independent of helm #889 (self-hosted Engine), which is still in review.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@baskaryan
Bagatur (baskaryan) marked this pull request as ready for review July 31, 2026 01:15
@baskaryan
Bagatur (baskaryan) requested a review from a team as a code owner July 31, 2026 01:15
Picks up the 0.16.0-rc.23 / 0.16.27rc1 chart cut (#890).

Conflicts: keep the engineInsightsAgentImage rename with v16-stable's bumped
tag; README regenerated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@baskaryan
Bagatur (baskaryan) merged commit 0898300 into v16-stable Jul 31, 2026
6 checks passed
@baskaryan
Bagatur (baskaryan) deleted the feat/engine-self-hosted-v16-sandboxes branch July 31, 2026 01:45
Bagatur (baskaryan) added a commit that referenced this pull request Jul 31, 2026
…892)

* chore(langsmith): bump chart to 0.16.0-rc.25 / appVersion 0.16.29rc1

rc.24 pairs Engine support (#889) with 0.16.28rc1 images, which predate the
sandbox WebSocket auth fix. Enabling Engine on that chart gives runs that die
on SandboxConnectionError.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(langsmith): retarget the chart bump to appVersion 0.16.30rc1

Folded the 0.16.29rc1 cut into this one: 0.16.30rc1 is the first release that
publishes langsmith-insights-engine (langchainplus #32518), so a single chart
bump covers both the WebSocket fix and Engine becoming installable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bagatur (baskaryan) added a commit that referenced this pull request Aug 19, 2026
* feat(engine): bring Engine/Insights chart support to main

The Engine work landed on v16-stable and was never forward-ported, so
main has no Engine support at all and still ships langsmith-clio as the
insights image. v17 would ship without Engine.

Ports the Insights/Engine parts of #889, #893, #896, #897, #898, #900,
#901, #909, #910 and #914: the co-hosted deployment on the combined
image, the engine values block and its validate guards, Engine env for
the co-host and for smith-go, the kill-switch when Engine is off, and
the image rename across the insights templates and packaging scripts.

Reconstructed against main rather than cherry-picked. The branches have
diverged in both directions, so replaying commits would have dragged
v16's older image tags and superseded comments back over main.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(engine): trim the engine values comments

The README's Engine section carries the requirements and the workspace
guidance, so the value comments only need a line each.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants