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
module 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 modules
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 runtime module:
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
runtime.Capability calls the selected ExecutionBindingBackend.acquire() when
its native wrap_run scope opens and release() when the operation ends. It exposes
the resulting RuntimeLease only while that context is active. The module does
not create, retire, or destroy persistent resources, and it stores no backend
SDK object in an module session snapshot.
The Shell module 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 module 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.
When DIFY_AGENT_API_TOKEN is configured, every private control-plane request must carry the matching Dify API AGENT_BACKEND_API_TOKEN as a Bearer token.
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",
"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. If a
collector raises, the task logs the tenant, resource type, and resource ID and
continues with the other independent resources in the batch. After all resources
have been attempted, any failure makes the Celery task fail and prevents Agent
aggregate deletion. Failed RETIRED rows remain available for a later retry. A
failure to publish the Celery task is also propagated to the product caller. No
automatic retry or reconciliation is performed.
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. An effective reference is a binding in a normal App's
current draft or current published Workflow. This ownership check applies only
to implicit retirement of Workflow-only Agents. Explicit deletion of a roster
Agent or Agent App proceeds even while Workflows reference it.
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. RETIRED is the sole physical-deletion condition for a Home Snapshot;
Draft and Config Snapshot references are historical pointers and do not keep it
alive. After every external resource in a deletion batch succeeds, Dify API
hard-deletes the archived Agent together with its Drafts, Config Snapshots,
Config Revisions, debug-conversation mappings, and resource ledgers in one
database transaction. Workflow Agent bindings belong to
their Workflows and remain unchanged, so they may hold a dangling Agent ID after
explicit deletion. Dify Agent itself remains stateless.
A RETIRED Workspace without a RETIRED Binding cannot identify a backend
participant through which to destroy the Workspace. That state is a lifecycle
invariant violation and fails collection instead of being logged as success.
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.
The default Binding-download deadline chain leaves each caller time to receive
and normalize the lower layer's result: the sandbox CLI upload is 180 seconds,
DIFY_AGENT_BINDING_FILE_DOWNLOAD_COMMAND_TIMEOUT_SECONDS is 210 seconds,
Dify API's AGENT_BACKEND_BINDING_FILE_DOWNLOAD_TIMEOUT_SECONDS is 240 seconds,
and DIFY_AGENT_E2B_ACTIVE_TIMEOUT_SECONDS is 3600 seconds.
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. The standard temp variables TMPDIR, TMP, and TEMP
also point directly to workspace_dir, so the Workspace is both the command
cwd and temp space. 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. |
| OpenShell | Supported via directory copies on an operator-provided shared volume | Supported with image-backed default Homes, without shared-Workspace attachment | Binding and Workspace refs map to the same OpenShell sandbox, addressed by its stable name; snapshots live under home-snapshots/<tenant-digest>/ on the shared volume. Production deployments must use one workspace and one dedicated volume per tenant. |
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.
OpenShell shares the E2B shape: one sandbox per Binding, no
existing_workspace_ref, no 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. Its 3600-second default is
intentionally the same as DIFY_AGENT_RUN_TIMEOUT_SECONDS, but the two settings
remain independently configurable. Runtime resources pause on timeout, but this
resource setting does not own the Agent run terminal state. It is not a retention
TTL and does not delete paused resources or immutable snapshots.
See the Shell module for request composition, the Operations Guide for Local and E2B validation, and the OpenShell guide for OpenShell configuration and validation.