Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
11 KiB
Runtime resources
Dify separates persistent product resources from request-time execution:
- a Home Snapshot is immutable Agent-owned Home content;
- a Workspace is mutable working data owned by a product scope such as a conversation, Build Draft, or Workflow run;
- an Execution Binding is one materialized Agent participant, including its private Home and resumable session, attached to a Workspace;
- a RuntimeLease is operation-scoped access to the physical Binding.
AgentWorkspaceBinding.id is the participant, materialized Home, and persisted
Agenton session identity. agent_id identifies the source Agent. The same
Agent can therefore have multiple active Bindings in one Workspace:
each has an independent Home and session, while all may share Workspace files.
Home and Workspace are logically independent. A backend may still couple their physical representation. For example, current E2B maps one Binding and its Workspace to one E2B resource, while Local can attach multiple materialized Homes to one shared Workspace.
Runtime layer graph
Agent requests do not expose separate Home, Workspace, or Sandbox layers. Dify
API resolves the Binding selected by the product flow and sends its opaque
backend ref to the dify.runtime layer:
flowchart LR
EC["dify.execution_context<br/>request identity"]
RT["dify.runtime<br/>opaque backend_binding_ref"]
SH["dify.shell<br/>commands and jobs"]
EC --> SH
RT --> SH
DifyRuntimeLayer calls the selected ExecutionBindingBackend.acquire() when
its resource context opens and release() when the operation ends. It exposes
the resulting RuntimeLease only while that context is active. The layer does
not create, retire, or destroy persistent resources, and it stores no backend
SDK object in an Agenton session snapshot.
The Shell layer consumes RuntimeLease.commands and RuntimeLease.layout. It
tracks only request-local shell job ids and offsets.
Closing a run clears that job state; it does not retire the Binding.
State ownership
Dify API is the lifecycle ledger. It stores three resource records:
| Record | Meaning | Backend field |
|---|---|---|
agent_home_snapshots |
One immutable Home version owned by an Agent. | snapshot_ref |
agent_workspaces |
One mutable Workspace owned by a product scope. | backend_workspace_ref |
agent_workspace_bindings |
One materialized participant, private Home, and resumable session attached to a Workspace. | backend_binding_ref |
Backend refs are opaque strings interpreted only by the selected backend
adapter. Dify API stores the latest Agenton session snapshot on the Binding, but
it does not serialize RuntimeLease, SDK clients, credentials, or temporary
access tokens.
Dify Agent does not connect to the Dify product database and has no persistent resource registry. Its private control-plane endpoints create or destroy backend resources from requests made by Dify API. Redis run records and event streams are observability state, not the Home/Workspace/Binding ledger.
Creation and execution flow
Agent creation does not create a Home Snapshot. A config with no logical Home
Snapshot asks the selected backend to materialize its deployment-default Home
when the Binding is created. This default Home is mutable and private to the
Binding; it does not produce an agent_home_snapshots row or an implicit
snapshot ref.
Build Draft Apply uses POST /home-snapshots/from-binding: Dify Agent acquires
the exact source Binding, snapshots its materialized Home through the
backend-native operation, releases the lease, and returns a new opaque snapshot
ref. Dify API then stores a new immutable agent_home_snapshots row and records
its logical id on the resulting config version. There is no replay or fallback
when the source Binding is unavailable.
Before an Agent request, Dify API loads the specific product context. If it has no associated Binding, Dify API materializes one and saves the Binding id in the same database transaction. Otherwise it resolves only that Binding and validates its owner and config/Home generation. Missing, retired, or mismatched Bindings fail fast; Dify API does not search by Agent, Workspace, candidate count, or recency, and it does not create a replacement implicitly.
POST /execution-bindings accepts either an exact home_snapshot_ref or
null. An exact ref must be materialized without fallback; null selects the
backend's deployment-default Home. It returns opaque Binding and Workspace
refs. Every create request represents a new participant, even when the Agent,
Snapshot, config generation, and Workspace match another Binding. The request
composition contains:
{
"name": "runtime",
"type": "dify.runtime",
"config": {"backend_binding_ref": "opaque-backend-binding-ref"}
}
Each Agent request acquires that ref for the duration of the run and releases it afterward. Local release closes the operation's shellctl connection. E2B release also pauses the underlying E2B resource with memory preserved. A later request or Binding file operation acquires a new lease for the same Binding ref. If a backend confirms the resource is gone, acquisition fails; it does not create an empty replacement Workspace.
Retirement and collection
Retirement is a database transition from ACTIVE to RETIRED. It prevents new
product use without performing network I/O inside the caller's transaction.
Product lifecycle paths commit this transition synchronously. After the
transaction commits, one Celery task asks Dify Agent to destroy the physical
resources. A successful collector deletes the corresponding ledger row; a
failed collector logs the failure and leaves the RETIRED row available for a
future retry or reconciler.
The unified collect_agent_resources task is registered on normal Celery
workers and explicitly uses the existing retention queue. Standard workers
already consume that queue, so no dedicated Agent resource worker or new queue
is required. At a Workflow terminal event, the graph layer synchronously retires
and commits the run's Workspaces before enqueueing collection. When a Workflow
change may orphan Workflow-only Agents, the main product transaction commits
first; a fresh session then rechecks effective ownership and retires only Agents
that remain unowned.
Retiring a final Binding also retires its Workspace. Workspace collection destroys the physical Workspace through one Binding and then collects remaining materialized Homes. Home Snapshots are retired when their owning Agent is retired and are collected only after no draft or config snapshot references them. Celery performs physical collection only; it does not decide or perform the initial retirement. Dify Agent itself remains stateless.
There is currently no age-based TTL, periodic GC, or global orphan reconciler. Backend destroy operations are idempotent where supported. Dify API does not perform cross-system compensation after a backend create returns success. Any later API failure, including Python, flush, or commit failure, may leave a physical orphan for a future global reconciler.
Backends still clean up partial resources when a create operation fails before returning success. For example, E2B kills a Sandbox when its initialization fails, and Local removes paths created by an incomplete operation. This backend-local cleanup does not cross the database commit boundary.
Binding file boundary
Dify API's public file APIs accept a product locator, not a Binding id or backend ref: a Conversation, a debug Build Draft, or a Workflow Node Execution. Dify API authorizes that object and resolves its associated active Binding. It does not select the latest Binding or fall back to another product context.
The resolved request reaches Dify Agent through its private
POST /execution-bindings/files/list, POST /execution-bindings/files/read,
and POST /execution-bindings/files/download endpoints. Each operation receives a
backend_binding_ref, acquires a fresh RuntimeLease, performs the file action,
and releases the lease.
BindingFileService resolves relative paths from workspace_dir, ~ and
~/... from home_dir, and leaves absolute paths in the Binding filesystem
namespace. It does not enforce Workspace containment or reject ..; the
selected backend's isolation policy remains authoritative. List and preview
run bounded inspection scripts through RuntimeLease.commands. Download runs
dify-agent file upload --no-download-link inside the Binding so bytes stream
directly from the runtime to Dify's existing ToolFile endpoint. Dify Agent
returns only the canonical ToolFile reference and releases the lease before
Dify API signs a browser URL.
RuntimeLayout.home_dir and RuntimeLayout.workspace_dir are canonical paths
inside the backend execution namespace. They are not host paths, product ids,
or request configuration. Shell commands start in workspace_dir, and HOME
is forced to home_dir. On Local, sibling materialized Homes may exist in the
same shellctl namespace, while path isolation restricts the active lease to its
own Home plus the shared Workspace.
Backend support
| Backend | Home Snapshot operations | Binding operations | Physical relationship |
|---|---|---|---|
| Local | Supported | Supported, including default empty Homes and attaching multiple Bindings to one Workspace | Snapshot directory, per-Binding materialized Home, and Workspace directory are separate. |
| E2B | Supported | Supported with template-backed default Homes, without shared-Workspace attachment | Binding and Workspace refs map to the same E2B resource; checkpoints use E2B snapshots. |
| Enterprise | Not implemented | Default-Home Binding creation, acquire, and coupled destroy are supported | Binding and Workspace refs map to one Gateway sandbox. Explicit Home Snapshot materialization fails fast. |
Local creates a new Home for every Binding id. Destroying one Binding without
the Workspace leaves sibling Homes and the shared Workspace intact. Current E2B
rejects existing_workspace_ref with shared_workspace_unsupported, because
its Binding and Workspace are one Sandbox. It also rejects binding-only destroy.
Neither path creates a fallback Workspace or switches backends.
DIFY_AGENT_E2B_ACTIVE_TIMEOUT_SECONDS limits continuous active time for an E2B
resource to one hour. The limit covers the complete Agent run held by one
RuntimeLease rather than an individual tool call. Runtime resources pause on
timeout. It is not a retention TTL and does not delete paused resources or
immutable snapshots.
See the Shell layer for request composition and the Operations Guide for Local and E2B validation.