feat(dify-agent): add an opt-in Rust local runtime canary - #40477
Closed
pineapple880066 wants to merge 3 commits into
Closed
feat(dify-agent): add an opt-in Rust local runtime canary#40477pineapple880066 wants to merge 3 commits into
pineapple880066 wants to merge 3 commits into
Conversation
pineapple880066
marked this pull request as ready for review
August 14, 2026 03:18
Member
|
Thank you for taking the time to contribute to Dify. This pull request currently has merge conflicts with the latest |
5 tasks
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.
Important
Fixes #<issue number>.Note
This PR is intentionally opened as a draft for architecture and scope
feedback. It is not ready for final review. Issue #39976 contains the full
benchmark methodology and results; the issue is currently unassigned.
Summary
Fixes #39976
This draft adds a protocol-compatible Rust implementation of
dify-agent-runtimebehind an explicit, sticky canary indify-agent. Theexisting Go runtime remains the default in the base Compose deployment and
continues to own every legacy, unprefixed resource.
The goal is not to rewrite Dify in Rust. It is to evaluate a narrow replacement
for local shell-runtime process management, where matched-container and full
Agent v2 tests show a measurable latency and memory difference. The additional
Rust toolchain and dependency surface are the main tradeoffs.
What changed
shellctl,shellctl-runner,shellctl-runner-exit, andshellctl-sanitize-pty.output offsets, interactive input, termination/deletion, and Linux Landlock
behavior.
base and user-facing Node, pnpm, uv, tmux, CLI, and shell tooling as Go.
the Rust service and routing are enabled by a separate Compose overlay.
opaque refs carry a
rust+prefix; existing and new Go refs retain the legacyunprefixed format.
/healthzpreflight. A failed preflight assigns a new,still-unowned Binding to Go before any Rust mutation begins.
lifecycle. Mixed-runtime refs are rejected before mutation.
or destroy operation begins because those operations are not generally
idempotent.
tests, Rust CI, Docker build entries, and a paired benchmark harness.
session/pipe startup, cheap completion-artifact polling with lower-frequency
tmux liveness probes, and reusable sanitizer buffers.
New configuration:
Rollout and rollback behavior
docker-compose.yamlremains Go-only.docker-compose.rust-runtime.yamlstarts an independent Rustsandbox. Go and Rust do not share SQLite, tmux, Home, Workspace, or Snapshot
state.
1%,5%,25%, or another value beforeincreasing to
100%.0%stops new Rust allocations immediately.Existing
rust+resources remain pinned to Rust and can drain safely.drained. Removing it earlier would intentionally fail instead of replaying
stateful operations in Go.
path. Legacy and canary-era Go refs require no migration.
Automatic fallback is intentionally limited to the pre-mutation health check
for a new unowned Binding.
Full Agent v2 Linux shadow A/B
To test more than the shellctl microbenchmark, I ran the complete Agent v2
/execution-bindings->/runs-> events/status ->/workspace/files/read-> binding-destroy path on a self-hosted Linux Difyhost. Temporary exact-image shadow containers had no host ports, received no
production traffic, and did not restart or replace production containers.
The test used a deterministic in-process model to remove provider latency, two
warm-up pairs, and 20 alternating measured Go/Rust pairs. Every sample used a
fresh Binding and Workspace and executed five sequential shell actions,
including 32 KiB file/stdout paths. Every tool result, exit code, terminal
state, marker, and file payload was validated. Confidence intervals use a
10,000-resample paired bootstrap.
Rust was faster in all 20 paired full-session samples. Full-session p95 was
2,126.475 ms for Go and 691.185 ms for Rust.
This production-host experiment used Dify source
8e74d7d715c88f1bd0a681cb275a8f7811522c97, while this PR is rebased ontocurrent
mainat48451f5adf1171c673e37f8a3871313eea940826. The PR tip hasseparately passed the clean build and compatibility tests listed below, but I
do not present the older deployment timing as an exact-tip benchmark.
The matched Linux-container microbenchmark reported in #39976 independently
measured approximately 4.6-5.9x lower short-job latency and 48.4% lower idle
long-lived-process RSS. These measurements cover local runtime overhead; they
do not represent external LLM/provider, database, vector-search, or general UI
latency.
Validation on the current PR tip
mainat48451f5adf1171c673e37f8a3871313eea940826.go test -race -count=1 ./...passed;golangci-lintreportedzero issues.
rust:1.95-bookwormLinux container: cleanlocked dependency resolution, formatting, Clippy with
-D warnings, releasebuild, and all 17 direct unit tests passed. The added cases cover state
transitions, concurrent runner-exit CAS, restart reconciliation, incomplete
exit markers, UTF-8 windows, atomic writes, sanitization, and helper contracts.
dify-agent: Ruff passed; scoped BasedPyright reported 0 errors/warnings;the rollout/backend suite passed with
73 passed; the complete test suitepassed with
777 passed, 30 skippedand one existing deprecation warning.New coverage includes canary bounds, sticky ownership, malformed refs,
snapshot routing, no post-mutation replay, and health/close failure ordering.
memory parsing, paired aggregation, deterministic bootstrap, fresh health
probes, and dynamic Docker port parsing.
images. All 35 test groups passed, including exact auth/validation errors,
env/cwd, non-zero exits, zero/default limits, large UTF-8 output reassembly,
wait/offset/tail/status/list, terminal conflicts, idempotency, eight-way
concurrency per runtime, dedicated Rust container restart recovery, and
Landlock allow/deny/bypass behavior. Go and Rust no-isolation variants also
passed.
dify-agentrollout integration passed: 100% Rust canary and snapshotrestore, Go/Rust state isolation, and unavailable-Rust preflight fallback to
real Go (
3 passed, 1 deselected; the deselected case requires E2B).workflow YAML parses locally;
git diff --checkpasses.run.
9bf8ac12de0193fab3e53cb2f5aedb0bb1ec441b: all 23 executed Main CI jobs completed successfully (7 conditional skips), including Python/style, API unit/integration, Web unit/E2E, database migration, VDB, and all four sandbox-runtime jobs. The selected Rust/Go sandbox, API, and Web Docker builds also passed, as did Semantic Pull Request and autofix.ci.Review scope
This draft intentionally presents the complete safe rollout slice so the
ownership and fallback semantics can be reviewed end to end. If that is too
large, I can split it into:
harness; and
Screenshots
Not applicable; this changes the local sandbox runtime, routing, deployment,
and CI behavior only.
Checklist
make lint && make type-check(backend) andcd web && pnpm exec vp staged(frontend) to appease the lint godsmake lintandmake type-checkpassed locally. The template's literal frontend working directory is stale: thestagedconfig now lives in the repository-rootvite.config.ts, so the root-equivalentpnpm exec vp stagedwas used. Because this PR has no frontend changes and no pending staged files, I also ran stronger controlled-path checks: Vite+ formatting over 7,964 files, Oxlint, and ESLint all completed successfully. API and frontend remain unchanged.From Codex