# langgenius/dify — codebase unpack

> Canonical page: https://codevetter.com/unpack/dify

This is a deterministic Repo Unpack scan of langgenius/dify — a structural map, not a review, security audit, or maintainer-endorsed description.

- Scanned commit: 3a017e8cccb316f23f601af65d2e8ce92e68f788 (2026-09-22)
- Last upstream commit: perf(ci): reuse coverage XML totals for API summary (#42745)
- Generated by codevetter 1.12.0 on 2026-09-22
- 4000 files scanned · 27.1 MB

## Languages

- TypeScript: 6845 files
- Python: 4101 files
- JSON: 1002 files
- Markdown: 178 files
- JavaScript: 136 files
- YAML: 128 files
- CSS: 68 files
- Go: 66 files

## Repository structure

- api/ — 928 files
- packages/ — 928 files
- web/ — 927 files
- cli/ — 362 files
- dify-agent/ — 290 files
- e2e/ — 155 files
- docker/ — 82 files
- dify-agent-runtime/ — 73 files

Probable source entrypoint: `packages/` (inferred from the tree, not upstream docs)

## Package

- package.json: dify — 33 declared dependencies

## Structural health signals

Average score 6.5/10 across 96 files; 45 hotspots. These are deterministic review leads, not proof of a bug.

## Recent history

- 3a017e8cccb3 (2026-09-22) perf(ci): reuse coverage XML totals for API summary (#42745)
- 1fc040183c62 (2026-09-22) chore: replace tsx with native Node TypeScript execution (#42742)
- 5b6e850b250e (2026-09-22) refactor(api): thin console app controllers (#42511)
- ce2eaab9f732 (2026-09-22) refactor(api): move app-scoped end users behind application services (#41398)
- 9badde4d655c (2026-09-22) fix(marketplace): support single-page carousel navigation (#42743)

## Overview

Dify is an open-source platform for building, hosting, and operating LLM applications — chatbots, RAG pipelines, agentic workflows, and MCP services — exposed through four HTTP surfaces (console admin, end-user webapp, service API, and MCP/trigger/webhook ingress). The monorepo contains a Flask/SQLAlchemy/Celery Python backend (`api/`, served via gevent gunicorn at :5001), a Next.js standalone console (`web/`), a generated-contract pnpm workspace (`packages/contracts` feeds an oRPC `consoleClient`), a Go job-sandbox runtime (`dify-agent-runtime/`), a Python agent backend (`dify-agent/`), and a Node/TS CLI (`cli/`, difyctl). Everything is deployed via generated Docker Compose with Postgres+Redis plus a pluggable fleet of ~28 vector stores, object stores, an external plugin daemon, and Squid SSRF proxies. The codebase is heavily productionized: license gating, quota/rate limiting, SSRF protection, human-in-the-loop pause/resume, and edition-aware (community/cloud/enterprise) feature flags are woven into the request path.

## System map

The system is a modular monolith API (Flask + flask-restx + SQLAlchemy + Celery) plus a Next.js console, with execution-heavy work pushed to Celery workers and sidecar services (plugin daemon, sandbox, agent runtime, SSRF proxy). `api/app.py` is the process entrypoint; `app_factory.create_app()` builds a `DifyApp`, runs an ordered list of ~30 `extensions/ext_*` initializers, and wraps the result in `socketio.WSGIApp`. HTTP ingress is split across eight Flask blueprints registered in `ext_blueprints.py`, each with its own CORS/auth surface. State lives in Postgres (Alembic, 219 migration files), Redis (broker, cache, rate limits, pub/sub for SSE), pluggable object storage, and pluggable vector DBs loaded via Python entry points.
- `api/app.py` is the API process entrypoint: when run as `python -m app` it gevent-monkey-patches stdlib, grpc, and psycopg2 BEFORE other imports, then serves the socketio-wrapped app on 0.0.0.0:5001 via `gevent.pywsgi.WSGIServer` with `WebSocketHandler`. — [api/app.py#L11-21](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app.py#L11-L21) [api/app.py#L75-81](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app.py#L75-L81)
- `app_factory.create_app()` returns a `(socketio.WSGIApp, DifyApp)` pair; `initialize_extensions()` iterates a fixed ordered list of ~30 `extensions/ext_*` modules (timezone → logging → database → redis → storage → key_provider → celery → login → blueprints → otel …) calling `init_app(app)` on each, skipping any ext exposing `is_enabled() == False`. — [api/app_factory.py#L161-172](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app_factory.py#L161-L172) [api/app_factory.py#L175-259](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app_factory.py#L175-L259)
- All HTTP surfaces are Flask blueprints registered in `ext_blueprints.init_app`: `/v1` service_api, `/api` web (split CORS: unauthenticated embed `/chat-messages*` vs authenticated default), `/console/api`, `/files`, `/inner/api`, `/mcp`, `/triggers`, and an `/openapi` blueprint gated behind `dify_config.OPENAPI_ENABLED`. — [api/extensions/ext_blueprints.py#L28-121](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/extensions/ext_blueprints.py#L28-L121)
- `flask db` commands get a deliberately reduced app: `is_db_command()` detects the argv shape and `create_migrations_app()` initializes only `ext_database`, `ext_migrate`, and `ext_commands` — Alembic over `api/migrations` (219 version files). — [api/app.py#L37-40](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app.py#L37-L40) [api/app_factory.py#L262-271](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app_factory.py#L262-L271)
- The Celery worker entrypoint `celery_entrypoint.py` applies the same gevent/grpc/psycogreen patching then imports `app, celery`; `ext_celery.init_app` creates a Celery app with a `FlaskTask` base class that wraps every task in `app.app_context()` plus request logging context, and stores it in `app.extensions["celery"]`. — [api/celery_entrypoint.py#L1-13](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/celery_entrypoint.py#L1-L13) [api/extensions/ext_celery.py#L118-169](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/extensions/ext_celery.py#L118-L169)
- Configuration is a single composed `DifyConfig` pydantic-settings class built from mixins (PackagingInfo, DeploymentConfig, FeatureConfig, MiddlewareConfig, ExtraServiceConfig, ObservabilityConfig, RemoteSettingsSourceConfig, EnterpriseFeature/TelemetryConfig) with source precedence init > env > remote-settings > .env > file secrets > pyproject TOML. — [api/configs/app_config.py#L60-118](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/configs/app_config.py#L60-L118)
- Remote configuration is an extension point: `RemoteSettingsSourceFactory` reads `REMOTE_SETTINGS_SOURCE_NAME` and delegates to `ApolloSettingsSource` or `NacosSettingsSource` to hydrate every model field. — [api/configs/app_config.py#L24-57](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/configs/app_config.py#L24-L57) [api/configs/remote_settings_sources](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/configs/remote_settings_sources)
- The Flask app object is a `DifyApp` subclass carrying typed extension attributes (e.g. `login_manager: DifyLoginManager`); config is pushed into `app.config` from `dify_config.model_dump()` and `RESTX_ERROR_404_HELP` is disabled so flask-restx 404s don't enumerate routes to anonymous callers. — [api/dify_app.py#L11-14](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/dify_app.py#L11-L14) [api/app_factory.py#L105-111](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app_factory.py#L105-L111)
- Object storage is abstracted by `extensions/ext_storage.py`: `Storage.get_storage_factory` matches `STORAGE_TYPE` to per-provider runners (S3, OpenDAL, local fs, Azure Blob, Aliyun OSS, GCS, Tencent COS, Oracle OCI, Huawei OBS, Baidu OBS, Supabase, TOS, …) implemented under `api/extensions/storage/`. — [api/extensions/ext_storage.py#L14-50](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/extensions/ext_storage.py#L14-L50) [api/extensions/storage](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/extensions/storage)
- Vector DBs are hollow workspace packages under `api/providers/vdb/vdb-*` that register `dify.vector_backends` Python entry points (e.g. vdb-pgvector → `PGVectorFactory`); ~28 backends exist and `api/pyproject.toml` defines per-backend uv dependency groups (`vdb-all`, `vdb-qdrant`, …) plus a parallel `providers/trace/*` workspace for tracing backends. — [api/providers/vdb/vdb-pgvector/pyproject.toml#L1-15](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/providers/vdb/vdb-pgvector/pyproject.toml#L1-L15) [api/pyproject.toml#L58-103](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/pyproject.toml#L58-L103) [api/pyproject.toml#L226-308](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/pyproject.toml#L226-L308)
- The graph execution engine is an external pinned dependency `graphon==0.7.0` (not in-repo): `WorkflowEntry` composes `graphon.Graph`, `GraphEngine`, `VariablePool`, `GraphRuntimeState`, command channels, and engine layers; Dify-owned nodes under `core/workflow/nodes/` cover agent, agent_v2, datasource, human_input, knowledge_index/retrieval, and trigger_* types. — [api/pyproject.toml#L48](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/pyproject.toml#L48) [api/core/workflow/workflow_entry.py#L36-50](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/workflow/workflow_entry.py#L36-L50) [api/core/workflow/nodes](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/workflow/nodes)
- Deployment is a generated `docker/docker-compose.yaml` (regenerated by `docker/generate_docker_compose` from `docker-compose-template.yaml` + envs) running api, worker, worker_beat, scheduler, web, postgres, mysql, redis, `dify-sandbox:0.2.15`, `dify-plugin-daemon:0.6.10-local`, `dify-agent-backend`, `dify-agent-local-sandbox`, two Squid SSRF proxies, certbot and nginx. — [docker/docker-compose.yaml#L1-10](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/docker/docker-compose.yaml#L1-L10) [docker/docker-compose.yaml#L206-773](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/docker/docker-compose.yaml#L206-L773)
- The web tier is a Next.js standalone build (`output: 'standalone'`, `typescript.ignoreBuildErrors: true`) using Turbopack rules, `transpilePackages` for echarts/zrender/env libs, and `serverExternalPackages: ['loro-crdt']`; base path and dev origins come from `web/env.ts`. — [web/next.config.ts#L1-41](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/web/next.config.ts#L1-L41) [web/package.json#L12-24](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/web/package.json#L12-L24)
- The frontend talks to the console API through a generated oRPC client: `web/service/console/index.ts` builds `consoleClient`/`consoleQuery` over `consoleRouterContract` from `@dify/contracts`, with a `DynamicLink` switching between browser transport and a server-side link registered on `globalThis.$consoleServerLink`. — [web/service/console/index.ts#L1-37](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/web/service/console/index.ts#L1-L37) [packages/contracts/package.json#L36-46](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/packages/contracts/package.json#L36-L46)
- Backend↔plugin-daemon traffic is plain HTTP: `core/plugin/impl/base.py` `BasePluginClient` issues requests against `PLUGIN_DAEMON_URL`/`PLUGIN_DAEMON_KEY` config; specialized clients exist per capability (agent, tool, model, datasource, endpoint, oauth, trigger, dynamic_select, debugging, asset). — [api/core/plugin/impl/base.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/plugin/impl/base.py) [api/core/plugin/impl/agent.py#L13-40](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/plugin/impl/agent.py#L13-L40) [api/configs/feature/__init__.py#L228-238](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/configs/feature/__init__.py#L228-L238)

## Feature catalog

User-facing capability is organized around typed `AppMode`s, each with its own generator under `core/app/apps/*` and matching controller surface. The console blueprint exposes the full authoring/admin surface (apps, datasets/knowledge, plugins, triggers, MCP servers, agent apps, billing/features, auth). The `/api` web blueprint serves published end-user apps; `/v1` service_api serves token-authed programmatic access; `/mcp`, `/triggers`, and `/inner/api` handle protocol ingress. Async work (indexing, mail, cleanup, triggers, telemetry) is a large Celery task fleet under `api/tasks/` and `api/schedule/` with a feature-flagged beat schedule.
- `AppMode` enumerates the product's app types: `completion`, `workflow`, `chat`, `advanced-chat`, `agent-chat` (legacy ReAct), `agent` (new Agent app bound 1:1 to a roster Agent via `Agent.app_id`), `channel`, and `rag-pipeline`. — [api/models/model.py#L374-398](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/models/model.py#L374-L398)
- Console app authoring APIs live under `controllers/console/app/`: app CRUD/import/export (`app.py`, `app_import.py`), workflow editing/runs/draft variables (`workflow.py`, `workflow_run.py`, `workflow_draft_variable.py`), messages/conversations, annotations, site publish settings, model config, ops tracing, statistics, MCP server exposure (`mcp_server.py`), and agent-app feature/access/sandbox controllers. — [api/controllers/console/app](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/console/app)
- Published-app runtime APIs live under `controllers/web/`: `/chat-messages` (+ `/stop`), `/completion-messages` (+ `/stop`), conversations, messages, saved messages, audio (TTS/STT), file upload, passport issuance, site info, forgot-password, human-input forms, and workflow event streams — all behind `WebApiResource`'s JWT passport decorator. — [api/controllers/web/completion.py#L98-294](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/web/completion.py#L98-L294) [api/controllers/web/wraps.py#L166-167](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/web/wraps.py#L166-L167)
- The service API (`/v1`, `controllers/service_api/`) exposes app execution, dataset management, end-user, and workspace resources authenticated by `ApiToken` bearer tokens with token-usage recording and single-flight token caching (`fetch_token_with_single_flight`). — [api/controllers/service_api/wraps.py#L1-67](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/service_api/wraps.py#L1-L67) [api/services/api_token_service.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/services/api_token_service.py)
- Knowledge/dataset features are under `controllers/console/datasets/`: datasets, documents, segments, hit-testing, metadata, website crawl, external knowledge bases, and a `rag_pipeline` subpackage; indexing runs through Celery tasks like `document_indexing_task` and `batch_create_segment_to_index_task`. — [api/controllers/console/datasets](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/console/datasets) [api/tasks/document_indexing_task.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/tasks/document_indexing_task.py) [api/tasks/batch_create_segment_to_index_task.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/tasks/batch_create_segment_to_index_task.py)
- Plugin lifecycle is a first-class feature: `services/plugin/` provides endpoint management, OAuth, auto-upgrade (`check_upgradable_plugin_task` beat), permission grants, dependency analysis, and data migration; marketplace integration is configured via `MARKETPLACE_ENABLED`/`MARKETPLACE_API_URL`. — [api/services/plugin](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/services/plugin) [api/extensions/ext_celery.py#L258-264](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/extensions/ext_celery.py#L258-L264) [api/configs/feature/__init__.py#L346-356](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/configs/feature/__init__.py#L346-L356)
- Trigger ingress exists at two layers: `controllers/trigger/` (webhook + schedule triggers) enqueue async work via `tasks/async_workflow_tasks.py`/`tasks/trigger_processing_tasks.py`, and workflow graphs can contain `trigger_plugin`, `trigger_schedule`, `trigger_webhook` node types gated by `is_trigger_node_type` — service/manual invokes of trigger-containing workflows are rejected by `TriggerWorkflowServiceModeUnavailableError`. — [api/controllers/trigger](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/trigger) [api/core/trigger/constants.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/trigger/constants.py) [api/services/app_generate_service.py#L360-368](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/services/app_generate_service.py#L360-L368)
- Apps can be exposed as MCP servers: `controllers/mcp/mcp.py` is a dedicated blueprint surface that is license-gated like `/v1` and gets `Forbidden(license_required)` on invalid enterprise licenses. — [api/controllers/mcp](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/mcp) [api/app_factory.py#L78-84](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app_factory.py#L78-L84)
- The Agent v2 surface spans repos: `dify-agent` (Python package, editable dep of api) provides runtime/protocol/adapters; `dify-agent-runtime` (Go) provides shellctl sandboxed job execution; console controllers `agent_app_access`/`agent_app_feature`/`agent_app_sandbox` plus `services/agent` manage roster agents and Composer bindings. — [dify-agent/src/dify_agent/__init__.py#L1-20](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/dify-agent/src/dify_agent/__init__.py#L1-L20) [dify-agent-runtime/internal/server](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/dify-agent-runtime/internal/server) [api/controllers/console/app/agent_app_sandbox.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/console/app/agent_app_sandbox.py) [api/models/model.py#L481-493](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/models/model.py#L481-L493)
- Tooling subsystem (`core/tools/`) supports builtin tools, custom (OpenAPI) tools, plugin tools, MCP tools, and workflow-as-tool wrapping, plus tool file manager and label manager. — [api/core/tools](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/tools)
- Content moderation is pluggable with three backends — `keywords`, `openai_moderation`, `api` — selected through `core/moderation/factory.py` for input and output moderation. — [api/core/moderation](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/moderation)
- Console auth covers password login, OAuth providers, email registration, forgot/reset password, activation invites, and data-source OAuth/bearer flows under `controllers/console/auth/`; sessions use flask-login via `ext_login`/`libs/login.py`. — [api/controllers/console/auth](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/console/auth) [api/extensions/ext_login.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/extensions/ext_login.py) [api/libs/login.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/libs/login.py)
- Human-in-the-loop is a product feature: `human_input` workflow nodes pause runs (`PauseStatePersistenceLayer`), forms are delivered over `controllers/*/human_input_form.py` + mail tasks (`mail_human_input_delivery_task`), and a beat-scheduled `human_input_form_timeout` task resumes timed-out runs. — [api/core/workflow/nodes/human_input](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/workflow/nodes/human_input) [api/core/app/layers/pause_state_persist_layer.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/app/layers/pause_state_persist_layer.py) [api/extensions/ext_celery.py#L252-257](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/extensions/ext_celery.py#L252-L257)
- `difyctl` CLI (`cli/`) ships commands for auth (login/logout/whoami/devices), config, env, workspace `use`, app `run`/`get`/`describe`/`export`/`import`/`create`/`delete`, `resume`, `set`, `skills`, and `version`, discovered by the generated `src/commands/tree.generated.ts` registry. — [cli/src/commands/tree.generated.ts#L1-10](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/cli/src/commands/tree.generated.ts#L1-L10) [cli/src/commands](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/cli/src/commands)
- Observability/tracing is a feature matrix: OpenTelemetry instrumentation (flask/celery/httpx/redis/sqlalchemy + B3 propagator) injects `X-Trace-Id`/`X-Span-Id` response headers post-request, and ops tracing integrates external providers via `core/ops` + `providers/trace/*` workspace packages. — [api/app_factory.py#L134-152](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app_factory.py#L134-L152) [api/pyproject.toml#L36-42](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/pyproject.toml#L36-L42) [api/pyproject.toml#L291-308](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/pyproject.toml#L291-L308)

## Data flow

Every generate request follows the same spine: blueprint → flask-restx Resource → auth decorator (JWT passport, session, API token, or inner-api key) → Pydantic payload validation → `services/` orchestration → `core/app/apps/*` generator → `graphon` GraphEngine events → Redis pub/sub → SSE stream to the caller. Writes go through SQLAlchemy sessions (Postgres) with explicit tenant scoping; large payloads and files go through `ext_storage` backends; model/tool/plugin invocations egress through the plugin daemon over HTTP; arbitrary outbound HTTP from nodes/tools is forced through the Squid SSRF proxy. Config flows from pydantic-settings (env/.env/TOML/remote) into `app.config` at boot.
- Web-app requests authenticate via `WebApiResource.method_decorators = [validate_jwt_token]` → `decode_jwt_token()` verifies a `PassportService` JWT carrying `app_code`/`app_id`/`end_user_id`, then loads `App`, `Site`, and `EndUser` rows and rejects disabled sites or missing entities. — [api/controllers/web/wraps.py#L26-44](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/web/wraps.py#L26-L44) [api/controllers/web/wraps.py#L47-102](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/web/wraps.py#L47-L102) [api/libs/passport.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/libs/passport.py)
- `ChatApi.post` (POST `/api/chat-messages`) gates on app mode (`CHAT|AGENT_CHAT|ADVANCED_CHAT|AGENT`), validates `ChatMessagePayload` (inputs/query/files/response_mode/conversation_id/parent_message_id), eagerly resolves the conversation, then calls `AppGenerateService.generate` and streams via `helper.compact_generate_response`. — [api/controllers/web/completion.py#L191-263](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/web/completion.py#L191-L263)
- `AppGenerateService.generate` wraps dispatch in `_run_with_guardrails`: on CLOUD edition it reserves `QuotaService.reserve(QuotaType.WORKFLOW)` with commit/refund semantics, then `RateLimit(app_model.id, max_active_requests)` bounds concurrent executions where the cap is `min(app.max_active_requests|APP_DEFAULT_ACTIVE_REQUESTS, APP_MAX_ACTIVE_REQUESTS)`. — [api/services/app_generate_service.py#L137-167](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/services/app_generate_service.py#L137-L167) [api/services/app_generate_service.py#L370-389](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/services/app_generate_service.py#L370-L389) [api/services/quota_service.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/services/quota_service.py)
- `_dispatch_generate` is a `match` on effective `AppMode` routing to `CompletionAppGenerator`, `AgentChatAppGenerator`, `AgentAppGenerator`, `ChatAppGenerator`, `AdvancedChatAppGenerator`, or `WorkflowAppGenerator` — each returning an event stream wrapped by `rate_limit.generate`. — [api/services/app_generate_service.py#L169-358](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/services/app_generate_service.py#L169-L358)
- Streaming workflow/advanced-chat runs cross a process boundary: the request path serializes `AppExecutionParams` (discriminated `Account`/`EndUser` union, app_id, workflow_id, tenant_id, args, call_depth, workflow_run_id) to JSON and enqueues `workflow_based_app_execution_task.delay(payload_json)` on the `workflow_based_app_execution` queue only when the first SSE subscriber attaches (`on_subscribe` + 200ms fallback timer; streams channel type skips the timer). — [api/services/app_generate_service.py#L249-278](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/services/app_generate_service.py#L249-L278) [api/services/app_generate_service.py#L55-97](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/services/app_generate_service.py#L55-L97) [api/tasks/app_generate/workflow_execute_task.py#L42-120](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/tasks/app_generate/workflow_execute_task.py#L42-L120)
- Inside the worker, `workflow_execute_task` rehydrates trusted state — reloads `App`, `Workflow`, and the user from Postgres via fresh sessions (never trusting the payload for identity beyond ids) — then drives `WorkflowAppGenerator`/`AdvancedChatAppGenerator` → `WorkflowEntry` → `graphon.GraphEngine.run()`, persisting `WorkflowRun`/`WorkflowNodeExecution` via `DifyCoreRepositoryFactory`/`DifyAPIRepositoryFactory`. — [api/tasks/app_generate/workflow_execute_task.py#L1-60](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/tasks/app_generate/workflow_execute_task.py#L1-L60) [api/repositories/factory.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/repositories/factory.py) [api/core/app/apps/workflow/app_generator.py#L72-120](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/app/apps/workflow/app_generator.py#L72-L120)
- Graph events are filtered before reaching the client: `iter_dify_graph_engine_events` pipes `engine.run()` through `filter_graph_events` with `HumanInputFormEventFilter` (backed by `HumanInputFormSubmissionRepository`) and `ResponseStreamFilter` which is deliberately shared with the pause-persistence layer so resumed runs keep consistent stream state. — [api/core/workflow/workflow_entry.py#L56-79](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/workflow/workflow_entry.py#L56-L79)
- Streaming responses are plain SSE: `compact_generate_response` returns a `Response` with `mimetype="text/event-stream"` wrapping `_stream_with_request_context(generate())` — blocking mode returns a JSON `Mapping` response instead. — [api/libs/helper.py#L416-435](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/libs/helper.py#L416-L435)
- A separate `length_prefixed_response` binary framing (1-byte magic + header/data length fields, little-endian) exists specifically for plugin-daemon compatibility, avoiding line-based streaming memory issues. — [api/libs/helper.py#L438-499](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/libs/helper.py#L438-L499)
- Model/tool invocations egress to the external plugin daemon via `BasePluginClient._request_with_plugin_daemon_response` (e.g. `PluginAgentClient.fetch_agent_strategy_providers` GETs `plugin/{tenant_id}/management/agent_strategies`); streamed blobs are reassembled by `merge_blob_chunks`. — [api/core/plugin/impl/base.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/plugin/impl/base.py) [api/core/plugin/impl/agent.py#L13-40](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/plugin/impl/agent.py#L13-L40)
- Untrusted outbound HTTP (HTTP Request nodes, provider integrations, auth discovery, custom tools) is forced through `core/helper/ssrf_proxy.py`: pooled httpx clients mounted per-scheme on `SSRF_PROXY_HTTP(S)_URL` (Squid), with retry-on-[429,5xx] backoff and response-size guards (`ResponseTooLargeError`, `UnsupportedResponseEncodingError`). — [api/core/helper/ssrf_proxy.py#L1-80](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/helper/ssrf_proxy.py#L1-L80) [docker/ssrf_proxy/squid.conf.template](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/docker/ssrf_proxy/squid.conf.template)
- Enterprise license enforcement is a `before_request` concern: for `DEPLOYMENT_EDITION == ENTERPRISE`, `_match_license_gated_surface` gates `/console/api`, `/api`, `/v1`, `/mcp`, `/triggers` with surface-specific errors (session surfaces force logout 401, bearer surfaces 403, webhook surfaces 503 so senders retry); `/files`, `/inner/api`, `/health` are deliberately ungated, and bootstrap endpoints (system-features, setup, init, login, features, profile, workspaces/current, version, activate/check) are exempted to avoid signin reload loops. — [api/app_factory.py#L24-51](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app_factory.py#L24-L51) [api/app_factory.py#L70-94](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app_factory.py#L70-L94) [api/app_factory.py#L113-130](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app_factory.py#L113-L130)
- Files flow through `factories/file_factory` (builders, remote fetch, storage keys, validation) into `ext_storage` backends; `core/app/file_access` provides `DatabaseFileAccessController` scoping file access, and remote-file retrieval is split off from the generic SSRF client into `core/file/remote_fetcher` so signed file URLs resolve via DB+storage first. — [api/factories/file_factory](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/factories/file_factory) [api/core/app/file_access/controller.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/app/file_access/controller.py) [api/core/helper/ssrf_proxy.py#L1-8](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/helper/ssrf_proxy.py#L1-L8)
- Async state crossing the broker is minimal and revalidated: `AppExecutionParams` carries only ids+args; `_ensure_workflow_service_mode_available` re-walks `workflow.walk_nodes()` for trigger nodes on manual invokes; `WORKFLOW_CALL_MAX_DEPTH` is enforced inside `WorkflowEntry.__init__` for nested workflow calls. — [api/tasks/app_generate/workflow_execute_task.py#L85-120](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/tasks/app_generate/workflow_execute_task.py#L85-L120) [api/services/app_generate_service.py#L360-368](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/services/app_generate_service.py#L360-L368) [api/core/workflow/workflow_entry.py#L138-141](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/workflow/workflow_entry.py#L138-L141)
- Stop/cancel flows go through Redis: `POST /chat-messages/<task_id>/stop` → `AppTaskService.stop_task` sets a cancellation signal that the running generator polls, raising `GenerateTaskStoppedError`; `clear_app_task_cancellation_signals` cleans up on worker completion. — [api/controllers/web/completion.py#L266-294](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/web/completion.py#L266-L294) [api/services/app_task_service.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/services/app_task_service.py) [api/tasks/app_generate/workflow_execute_task.py#L15](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/tasks/app_generate/workflow_execute_task.py#L15)

## Behavior traces

Three flows traced end-to-end: (1) API server startup, (2) end-user streaming chat message across the request→Celery→SSE boundary, and (3) the `flask db` migration path plus Celery worker boot. The dominant pattern is 'authenticate at the edge, rehydrate trusted state inside the worker, stream events back through Redis'.
- STARTUP (gunicorn/prod): `api/docker/entrypoint.sh` → gunicorn gevent workers (`gunicorn.conf.py`) → import `app.py` → `app_factory.create_app()` → `create_flask_app_with_configs()` builds `DifyApp` + pushes `dify_config.model_dump()` into `app.config` + registers `before_request` (request-context init, enterprise license gate) and `after_request` (OTel trace headers) → `initialize_extensions()` runs the ordered ext list → `socketio.WSGIApp(sio, app)` → served on :5001. — [api/app.py#L51-73](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app.py#L51-L73) [api/app_factory.py#L100-172](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app_factory.py#L100-L172) [api/gunicorn.conf.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/gunicorn.conf.py) [api/docker/entrypoint.sh](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/docker/entrypoint.sh)
- STARTUP (bare `python -m app`): monkey.patch_all() → grpc_gevent.init_gevent() → psycogreen.patch_psycopg() → create_app() → `log_startup_banner` → `pywsgi.WSGIServer((0.0.0.0,5001), socketio_app, WebSocketHandler).serve_forever()`. — [api/app.py#L11-21](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app.py#L11-L21) [api/app.py#L75-81](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app.py#L75-L81)
- MIGRATION PATH: `flask db upgrade` → `is_db_command()` detects `sys.argv` → `create_migrations_app()` → `create_flask_app_with_configs()` → only `ext_database`, `ext_migrate`, `ext_commands` initialized → Flask-Migrate/Alembic runs `api/migrations/env.py` against the 219-version history. — [api/app.py#L37-40](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app.py#L37-L40) [api/app_factory.py#L262-271](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app_factory.py#L262-L271) [api/migrations/env.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/migrations/env.py)
- WORKER BOOT: celery invokes `celery_entrypoint.py` → gevent/grpc/psycopg patch → `from app import app, celery` (full create_app ran) → `ext_celery.init_app` built the Celery app earlier with `FlaskTask` (app_context + `init_request_context` per task) → `setup_workflow_warm_shutdown_handler()` → beat schedule assembled conditionally from ~20 `ENABLE_*`/edition flags (cleanup crons, tidb serverless, human-input timeouts, plugin upgrade checks, telemetry heartbeats). — [api/celery_entrypoint.py#L1-13](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/celery_entrypoint.py#L1-L13) [api/extensions/ext_celery.py#L118-171](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/extensions/ext_celery.py#L118-L171) [api/extensions/ext_celery.py#L186-319](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/extensions/ext_celery.py#L186-L319)
- CHAT MESSAGE (request side): POST `/api/chat-messages` → `validate_jwt_token` → `decode_jwt_token` → `extract_webapp_passport` header → `PassportService().verify` → DB loads `App`/`Site`/`EndUser` → webapp-auth mode checks (`_validate_webapp_token`, `_validate_user_accessibility` incl. SSO `granted_at` staleness) → `ChatApi.post` → mode gate → payload validate → eager `ConversationService.get_conversation` → `AppGenerateService.generate`. — [api/controllers/web/wraps.py#L47-163](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/web/wraps.py#L47-L163) [api/controllers/web/completion.py#L207-239](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/web/completion.py#L207-L239)
- CHAT MESSAGE (guardrails+dispatch): `generate` → `_run_with_guardrails` → `QuotaService.reserve` (CLOUD only) → `RateLimit.enter(request_id)` → `_dispatch_generate` → for `ADVANCED_CHAT`/`WORKFLOW` streaming: `_get_workflow` (published vs draft by invoke_from) → `_ensure_workflow_service_mode_available` (reject trigger nodes for manual invokes) → `rate_limit_context` → `AppExecutionParams.new(...).model_dump_json()` → `_build_streaming_task_on_subscribe` wraps `workflow_based_app_execution_task.delay` → `retrieve_events(app_mode, workflow_run_id, on_subscribe)` → `rate_limit.generate(convert_to_event_stream(...))` → `compact_generate_response` SSE. — [api/services/app_generate_service.py#L99-167](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/services/app_generate_service.py#L99-L167) [api/services/app_generate_service.py#L245-335](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/services/app_generate_service.py#L245-L335)
- CHAT MESSAGE (worker side): `workflow_based_app_execution_task` deserializes `AppExecutionParams` → opens fresh `Session` → reloads `App`/`Workflow`/user (`set_login_user` for flask-login context) → `WorkflowAppGenerator().generate` → `WorkflowAppConfigManager` + `WorkflowAppRunner` → `WorkflowEntry.__init__` (call-depth check, `InMemoryChannel` default, `capture_current_context`) → `GraphEngine.run()` → `iter_dify_graph_engine_events` filters → queue manager publishes to Redis → client SSE relay → finish persists `WorkflowRun`/node executions; `PauseStatePersistenceLayer` handles human-input pauses. — [api/tasks/app_generate/workflow_execute_task.py#L14-39](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/tasks/app_generate/workflow_execute_task.py#L14-L39) [api/core/app/apps/workflow/app_generator.py#L72-120](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/app/apps/workflow/app_generator.py#L72-L120) [api/core/workflow/workflow_entry.py#L102-149](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/workflow/workflow_entry.py#L102-L149) [api/core/app/layers/pause_state_persist_layer.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/app/layers/pause_state_persist_layer.py)
- DEBUG SINGLE-NODE: `AppGenerateService.generate_single_iteration`/`generate_single_loop` bypass the Celery hop entirely — they resolve the draft workflow via `_get_workflow(..., InvokeFrom.DEBUGGER)` and call `single_iteration_generate`/`single_loop_generate` on the advanced-chat/workflow generators synchronously in the request process. — [api/services/app_generate_service.py#L391-479](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/services/app_generate_service.py#L391-L479)
- INNER API AUTH: plugin-daemon/enterprise calls hit `/inner/api/*` → `inner_api_only` checks `dify_config.INNER_API` (404 if disabled) then constant-compares `X-Inner-Api-Key` against `INNER_API_KEY`; `enterprise_inner_api_user_auth` additionally accepts `Authorization: <user_id>:<hmac>` and injects an `EndUser`. — [api/controllers/inner_api/wraps.py#L17-60](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/inner_api/wraps.py#L17-L60) [api/controllers/inner_api/__init__.py#L6-16](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/inner_api/__init__.py#L6-L16)
- SERVICE API AUTH: `/v1/*` resources use flask-restx wrappers that extract the `Authorization` bearer `ApiToken`, cache via `ApiTokenCache`/`fetch_token_with_single_flight`, record token usage, and resolve the `EndUser`-equivalent caller from `user` query/json/form params per `FetchUserArg` policy. (inferred) — [api/controllers/service_api/wraps.py#L1-67](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/service_api/wraps.py#L1-L67) [api/services/api_token_service.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/services/api_token_service.py)
- STOP FLOW: `POST /chat-messages/<task_id>/stop` → `AppTaskService.stop_task(task_id, invoke_from, user_id, app_mode)` writes a Redis cancellation key → running task pipeline notices and raises `GenerateTaskStoppedError` → worker cleanup calls `clear_app_task_cancellation_signals`. (inferred) — [api/controllers/web/completion.py#L266-294](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/web/completion.py#L266-L294) [api/services/app_task_service.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/services/app_task_service.py) [api/core/app/apps/exc.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/app/apps/exc.py)
- EXTENSION INIT ORDER is load-bearing: `ext_key_provider` runs after `ext_storage` because `RSAKeyProvider` reads private keys from storage; `ext_logstore` runs after storage and before celery; `ext_import_modules` runs early so controller modules' route decorators evaluate before blueprint registration. — [api/app_factory.py#L214-246](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app_factory.py#L214-L246) [api/extensions/ext_key_provider.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/extensions/ext_key_provider.py)
- EDITION GATING: `DeploymentEdition` (COMMUNITY/CLOUD/ENTERPRISE) is checked at multiple layers — license gate in `before_request`, quota reserve in `_run_with_guardrails`, telemetry beat tasks, and `EnterpriseService`/`SystemFeatureService` lookups for webapp auth and billing features (`FeatureService.get_features` fulfills plan params from env, workspace info, or billing API). — [api/app_factory.py#L120-130](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app_factory.py#L120-L130) [api/services/app_generate_service.py#L145-150](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/services/app_generate_service.py#L145-L150) [api/services/feature_service.py#L9-59](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/services/feature_service.py#L9-L59) [api/services/enterprise/enterprise_service.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/services/enterprise/enterprise_service.py)

## Testing signals

Testing is layered and unusually heavy: ~1655 Python test files split into `unit_tests`, `integration_tests` (Docker-backed, CI-only), and `test_containers_integration_tests`, plus per-provider `vdb/*/tests` and `trace/*/tests` in the uv workspace. Web uses Vitest (+browser mode), e2e uses Cucumber+Playwright with accessibility (axe/WCAG) lanes, cli uses Vitest against a real Hono mock server, and the Go runtime has colocated `*_test.go`. CI shards API unit tests by historical durations and gates on codecov.
- `api/pytest.ini` configures coverage (`--cov=./api`, branch, json+xml reports), `--import-mode=importlib`, and `pytest-env` seeds dozens of fake provider keys plus `PLUGIN_DAEMON_URL`, `PLUGIN_DAEMON_KEY`, `INNER_API_KEY_FOR_PLUGIN`, and `MOCK_SWITCH=true` for hermetic unit runs. — [api/pytest.ini](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/pytest.ini)
- Backend tests live in `api/tests/` (1655 py files): `unit_tests/` mirrors the package tree (core, controllers, services, tasks, schedule, factories, migrations, tools, datasource, trigger, extension, app/layers/task_pipeline), `integration_tests/` covers controllers/services/workflow/model_runtime/tools/vdb/storage/tasks against real middleware, and `test_containers_integration_tests/` uses testcontainers. — [api/tests](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/tests) [api/tests/conftest.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/tests/conftest.py)
- `make test` runs unit tests with `pytest -p no:benchmark --timeout 20 -n auto` over `api/tests/unit_tests` + all `api/providers/*/tests/unit_tests`, running `unit_tests/controllers` in a second pass with `--cov-append`; `make test-all` adds `--start-middleware` integration runs and `--start-vdb` VDB smoke tests (chroma, pgvector, qdrant, weaviate). — [Makefile#L104-153](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/Makefile#L104-L153)
- CI `.github/workflows/api-tests.yml` downloads a shared timing baseline (`test-times.json` from a dify-test-infra stats repo) and runs `api/dev/pytest_sharding.py` to freeze per-shard file assignments, uploading the plan as an artifact for the shard jobs. — [.github/workflows/api-tests.yml#L14-53](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/.github/workflows/api-tests.yml#L14-L53) [api/dev/pytest_sharding.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/dev/pytest_sharding.py)
- A dedicated `.github/workflows/db-migration-test.yml` exists for migration correctness; codecov.yml at root configures coverage reporting (CODECOV_TOKEN secret plumbed through the reusable workflow). — [.github/workflows/db-migration-test.yml](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/.github/workflows/db-migration-test.yml) [codecov.yml](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/codecov.yml) [.github/workflows/api-tests.yml#L1-10](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/.github/workflows/api-tests.yml#L1-L10)
- Web tests are Vitest with a browser project (`vitest.browser.setup.ts`, `vitest.setup.ts`, `vitest-browser-react`, `vitest-canvas-mock` deps); specs are colocated (`web/service/base.spec.ts`, `web/service/common.spec.ts`, `web/service/__tests__/`) and `pnpm test` = `vp test --project unit`. — [web/package.json#L31](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/web/package.json#L31) [web/package.json#L176-194](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/web/package.json#L176-L194) [web/vitest.setup.ts](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/web/vitest.setup.ts)
- E2E is Cucumber+Playwright in `e2e/` (`cucumber.config.ts`, `features/{apps,auth,smoke,agent-v2,accessibility}`) with tag-selectable lanes including `@axe`/`@wcag-aa` accessibility scans and `@browser-smoke` Chromium+WebKit lanes; scripts orchestrate seed/reset/middleware/web-build lifecycle. — [e2e/package.json#L5-26](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/e2e/package.json#L5-L26) [e2e/features](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/e2e/features) [e2e/cucumber.config.ts](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/e2e/cucumber.config.ts)
- CLI tests are Vitest colocated with commands (`cli/src/commands/**/*.test.ts`, `coverage.test.ts`, `agent-guides.test.ts` for meta-coverage) and run against a real Hono mock server at `cli/test/fixtures/dify-mock` — AGENTS.md explicitly forbids nock/msw/fetchMock. — [cli/src/commands](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/cli/src/commands) [cli/test/fixtures/dify-mock](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/cli/test/fixtures/dify-mock)
- Go runtime tests are colocated (`internal/server/*_test.go`, `cmd/runner/main_test.go`, `packaging_test.go`) covering api/config/db/output/runtime/snapshot/tmux/service; landlock has build-split linux vs stub files suggesting sandbox behavior is only tested on Linux. — [dify-agent-runtime/internal/server](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/dify-agent-runtime/internal/server) [dify-agent-runtime/cmd/runner/main_test.go](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/dify-agent-runtime/cmd/runner/main_test.go) [dify-agent-runtime/internal/landlock/landlock_stub.go](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/dify-agent-runtime/internal/landlock/landlock_stub.go)
- Contract testing exists: `packages/contracts` holds `openapi-yaml.test.ts`, `service-api-contract.test.ts`, `knowledge-fs-contract.test.mjs`, `sandbox-contract.smoke.test.ts` validating generated TS clients against backend OpenAPI output; `api/dev/lint_response_contracts.py` (`make api-contract-lint`) lints Flask response docs against returned schemas. — [packages/contracts](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/packages/contracts) [api/dev/lint_response_contracts.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/dev/lint_response_contracts.py) [Makefile#L87-90](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/Makefile#L87-L90)
- Static gates beyond unit tests: `make lint` runs ruff format+check --fix + response-contract lint + `lint-imports` (import-linter module boundaries) + `dotenv-linter` over `api/.env.example`/`web/.env.example`; `make type-check` runs pyrefly (`python-platform=linux`, `infer-with-first-use`) + mypy excluding tests/migrations. — [Makefile#L68-102](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/Makefile#L68-L102) [api/pyproject.toml#L310-317](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/pyproject.toml#L310-L317)
- Integration tests are CI-owned per repo policy — `api/AGENTS.md` states docker-backed suites are not expected to run locally and agents must not start long-running services — so local verification is unit-test-only for most developers. — [api/AGENTS.md](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/AGENTS.md)
- Coverage gaps to note: web `typescript.ignoreBuildErrors: true` means type errors don't fail builds; the e2e suite is opt-in/tag-gated; `dify-agent` local tests live under `dify-agent/tests/local/` mirroring src with an explicit no-mock-for-real-boundaries rule. (inferred) — [web/next.config.ts#L21-23](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/web/next.config.ts#L21-L23) [dify-agent/AGENTS.md](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/dify-agent/AGENTS.md) [e2e/package.json](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/e2e/package.json)

## Risk map

The highest-risk surface is execution of untrusted work: SSRF-bearing HTTP egress, sandboxed code execution, shell job runtime with Landlock, and plugin-daemon RPC — all defended in depth but dependent on every caller routing through the right proxy. Auth is layered (session JWT, passport JWT, API tokens, shared inner-api key) with enterprise license gating able to fail closed across nearly every surface. Fragile spots: committed static test secrets, `ignoreBuildErrors` on the web build, a giant ordered extension-init list, and tenant-scoping discipline enforced by convention.
- SSRF is a designed-in risk domain: generic outbound HTTP must route through `core/helper/ssrf_proxy.py` (Squid at `SSRF_PROXY_*_URL`, `docker/ssrf_proxy/squid.conf.template`); remote file retrieval is split into `core/file/remote_fetcher` — any future code path using raw httpx bypasses egress policy. — [api/core/helper/ssrf_proxy.py#L1-13](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/helper/ssrf_proxy.py#L1-L13) [docker/ssrf_proxy](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/docker/ssrf_proxy) [api/AGENTS.md](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/AGENTS.md)
- Sandboxed execution: `dify-sandbox` is a separate container image for code nodes; `dify-agent-runtime` executes user shell jobs via tmux with Landlock filesystem restrictions — but `landlock_stub.go` implies no sandbox on non-Linux kernels, so dev/other-OS runs are unsandboxed. — [docker/docker-compose.yaml#L511](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/docker/docker-compose.yaml#L511) [dify-agent-runtime/internal/landlock/landlock_linux.go](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/dify-agent-runtime/internal/landlock/landlock_linux.go) [dify-agent-runtime/internal/landlock/landlock_stub.go](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/dify-agent-runtime/internal/landlock/landlock_stub.go) [dify-agent-runtime/cmd/runner/main.go#L1-16](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/dify-agent-runtime/cmd/runner/main.go#L1-L16)
- The entire `/inner/api` enterprise/plugin control plane hangs on one shared secret: `X-Inner-Api-Key` string-equality against `dify_config.INNER_API_KEY`, gated by the `INNER_API` flag (404 when off) — key rotation/leak blast radius covers workspace, agent, knowledge, and runtime-credentials endpoints. — [api/controllers/inner_api/wraps.py#L17-37](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/inner_api/wraps.py#L17-L37) [api/controllers/inner_api](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/inner_api)
- Enterprise license failure is fail-closed across `/console/api`, `/api`, `/v1`, `/mcp`, `/triggers`: unverifiable license (`None` on exception) produces the same denial as INACTIVE/EXPIRED/LOST — a license-service outage can take down the whole product except `/files`, `/inner/api`, `/health`. — [api/app_factory.py#L51-94](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app_factory.py#L51-L94) [api/app_factory.py#L120-130](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app_factory.py#L120-L130)
- Committed secret-shaped values in `api/pytest.ini` (PLUGIN_DAEMON_KEY, INNER_API_KEY_FOR_PLUGIN, ANTHROPIC/OPENAI keys) are test fakes but are static, long-lived, and grep-visible — risk of pattern-copy into real envs. (inferred) — [api/pytest.ini](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/pytest.ini)
- Web production builds cannot fail on type errors: `next.config.ts` sets `typescript.ignoreBuildErrors: true`; correctness relies on separate lint/type-check CI steps staying green. — [web/next.config.ts#L21-23](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/web/next.config.ts#L21-L23)
- Tenant isolation is enforced by convention, not framework: AGENTS.md mandates propagating `tenant_id` across every layer and re-scoping reads by 'the complete owner chain', and `decode_jwt_token`/`AppExecutionParams` rehydrate entities from DB after trust boundaries — a missed `tenant_id` filter in any new service method is a cross-tenant data leak. — [api/AGENTS.md](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/AGENTS.md) [api/controllers/web/wraps.py#L58-70](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/web/wraps.py#L58-L70) [api/tasks/app_generate/workflow_execute_task.py#L85-120](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/tasks/app_generate/workflow_execute_task.py#L85-L120)
- Celery payloads carry user ids and raw `args` through the Redis broker (`AppExecutionParams.model_dump_json()`); broker access reveals tenant inputs, and non-idempotent retried tasks would double side effects — AGENTS.md requires idempotency and bounded transactions (no external I/O inside open DB transactions). — [api/services/app_generate_service.py#L252-265](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/services/app_generate_service.py#L252-L265) [api/AGENTS.md](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/AGENTS.md)
- Route enumeration is a known concern: `RESTX_ERROR_404_HELP` is explicitly disabled because flask-restx appends URL-map suggestions to 404 bodies; `controllers/console/spec.py` and the `/openapi` blueprint expose generated specs (OPENAPI_ENABLED gated). — [api/app_factory.py#L107-111](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app_factory.py#L107-L111) [api/controllers/console/spec.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/console/spec.py) [api/extensions/ext_blueprints.py#L48-63](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/extensions/ext_blueprints.py#L48-L63)
- The ordered `initialize_extensions` list is fragile coupling: init order is load-bearing (key_provider after storage, logstore before celery, blueprints after everything) and an `is_enabled()` skip can silently drop a dependency of a later ext. (inferred) — [api/app_factory.py#L214-259](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app_factory.py#L214-L259)
- Blocking-vs-streaming asymmetry: Agent App (`AppMode.AGENT`) is SSE-only — `response_mode='blocking'` raises 400 — and debug single-node/loop runs execute in the request process, so a runaway node can pin a web worker greenlet. — [api/controllers/web/completion.py#L45-51](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/web/completion.py#L45-L51) [api/services/app_generate_service.py#L391-434](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/services/app_generate_service.py#L391-L434)
- Webapp auth has layered token semantics (`token_source`, `auth_type`, `granted_at` SSO-staleness checks against external/workspace SSO update timestamps); regressions in `_validate_user_accessibility` silently lock out or over-admit end users. — [api/controllers/web/wraps.py#L105-163](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/web/wraps.py#L105-L163)
- Credential confidentiality depends on `ext_key_provider`'s RSAKeyProvider (reads private keys from object storage at boot) and the optional `kms` dependency group (azure-keyvault); storage misconfiguration at startup poisons all credential decryption. — [api/extensions/ext_key_provider.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/extensions/ext_key_provider.py) [api/app_factory.py#L228](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app_factory.py#L228) [api/pyproject.toml#L192-198](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/pyproject.toml#L192-L198)
- Dead/legacy surfaces exist: `AppMode.CHANNEL`/`RAG_PIPELINE` are explicitly rejected by generation paths; `agent-chat` is documented as the legacy ReAct app superseded by `agent`; `tasks/collect_agent_resources_task.py` is marked 'retired' yet still imported by the worker. — [api/models/model.py#L374-385](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/models/model.py#L374-L385) [api/services/app_generate_service.py#L431-434](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/services/app_generate_service.py#L431-L434) [api/extensions/ext_celery.py#L175](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/extensions/ext_celery.py#L175)
- The editable cross-package dep `dify-agent = { path = "../dify-agent" }` means api version compatibility with the agent package is unpinned inside the lock beyond the workspace — drift between api and dify-agent breaks agent apps at runtime. (inferred) — [api/pyproject.toml#L62-64](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/pyproject.toml#L62-L64) [dify-agent/pyproject.toml](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/dify-agent/pyproject.toml)

## Extension points

Dify is built for extension at every layer: app modes via generator classes + a match-dispatch, workflow nodes via `DifyNodeFactory`/`resolve_workflow_node_class` (with most builtins now in the external `graphon` package), vector/trace backends via Python entry-point packages, storage via a `StorageType` match, remote config via named sources, HTTP routes via flask-restx Namespaces + module import lists, Celery work via the imports+beat_schedule assembly, moderation/tool/providers via factories and the external plugin daemon, CLI verbs via a generated command tree, and frontend calls via regenerated oRPC contracts.
- New app types plug in at three coordinated points: extend `AppMode` in `models/model.py`, add a generator under `core/app/apps/<mode>/` subclassing `BaseAppGenerator`, and add a `case` in `AppGenerateService._dispatch_generate`. — [api/models/model.py#L374-385](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/models/model.py#L374-L385) [api/services/app_generate_service.py#L188-358](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/services/app_generate_service.py#L188-L358) [api/core/app/apps](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/app/apps)
- Workflow node types resolve through `core/workflow/node_factory.py` (`DifyNodeFactory`, `resolve_workflow_node_class`, `is_start_node_type`); Dify-owned node dirs (`agent`, `agent_v2`, `datasource`, `human_input`, `knowledge_index`, `knowledge_retrieval`, `trigger_*`) coexist with `graphon.nodes.BuiltinNodeTypes` from the external engine package. — [api/core/workflow/node_factory.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/workflow/node_factory.py) [api/core/workflow/nodes](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/workflow/nodes) [api/core/workflow/workflow_entry.py#L19-24](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/workflow/workflow_entry.py#L19-L24)
- Vector backends are true plugins: each `api/providers/vdb/vdb-*` package declares `[project.entry-points."dify.vector_backends"]` (e.g. `pgvector = "dify_vdb_pgvector.pgvector:PGVectorFactory"`) and joins the uv workspace via `tool.uv.workspace.members`. — [api/providers/vdb/vdb-pgvector/pyproject.toml#L12-14](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/providers/vdb/vdb-pgvector/pyproject.toml#L12-L14) [api/pyproject.toml#L58-61](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/pyproject.toml#L58-L61)
- Tracing providers mirror the vdb pattern: `api/providers/trace/*` workspace packages (aliyun, arize-phoenix, langfuse, langsmith, mlflow, opik, tencent, weave) selected via `trace-*` dependency groups. — [api/pyproject.toml#L291-308](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/pyproject.toml#L291-L308)
- Storage backends extend `StorageType` + a `case` in `Storage.get_storage_factory` returning a `BaseStorage` runner; implementations live in `api/extensions/storage/` (s3, opendal, azure_blob, gcs, oss, cos, oci, obs, baidu, supabase, tos, clickzetta_volume). — [api/extensions/ext_storage.py#L21-50](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/extensions/ext_storage.py#L21-L50) [api/extensions/storage](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/extensions/storage)
- Config additions go into the right `configs/` mixin group (deploy/enterprise/extra/feature/middleware/observability/packaging/remote_settings_sources) and are picked up by pydantic-settings; remote sources extend `RemoteSettingsSourceName` + a `case` in `RemoteSettingsSourceFactory`. — [api/configs/app_config.py#L40-47](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/configs/app_config.py#L40-L47) [api/configs/app_config.py#L89-92](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/configs/app_config.py#L89-L92) [api/configs](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/configs)
- New HTTP routes: add a flask-restx `Namespace` resource module under the right `controllers/` blueprint package; console modules must be imported in `controllers/console/__init__.py` (`RESOURCE_MODULES` tuple or the `from . import …` blocks) so route decorators evaluate. — [api/controllers/console/__init__.py#L40-70](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/console/__init__.py#L40-L70) [api/extensions/ext_blueprints.py#L28-38](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/extensions/ext_blueprints.py#L28-L38)
- New Celery work: add module path to `ext_celery.init_app`'s `imports` list, gate it with an `ENABLE_*` flag in `CeleryScheduleTasksConfig`, and add a `beat_schedule` entry for periodic jobs — the file explicitly asks contributors to add the switch. — [api/extensions/ext_celery.py#L172-188](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/extensions/ext_celery.py#L172-L188)
- Model/tool/agent-strategy providers live in the external plugin daemon, not this repo: new capabilities surface to the API through `core/plugin/impl/*` clients and are installed via marketplace; local extension is via `ext_code_based_extension` + `api_based_extension` external-data-tool hooks. — [api/core/plugin/impl](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/plugin/impl) [api/extensions/ext_code_based_extension.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/extensions/ext_code_based_extension.py) [api/services/api_based_extension_service.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/services/api_based_extension_service.py)
- Moderation backends plug into `core/moderation/factory.py` with existing `keywords`, `openai_moderation`, and `api` implementations as templates. — [api/core/moderation](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/moderation)
- CLI verbs: create `cli/src/commands/<topic>/<verb>/index.ts` extending `DifyCommand` (sibling `run.ts`/`handlers.ts`/`guide.ts` optional; `_shared/` excluded from discovery), then `pnpm tree:gen` regenerates `tree.generated.ts` — CI gates drift with `tree:check`. — [cli/src/commands/tree.generated.ts#L1-4](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/cli/src/commands/tree.generated.ts#L1-L4) [cli/src/commands](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/cli/src/commands)
- Frontend↔backend contract extension is generated, not handwritten: new console endpoints flow through `api/controllers` schemas → `api/dev/generate_swagger_specs.py`/`generate_fastopenapi_specs.py` → `packages/contracts` `gen-api-contract` (openapi-ts) → consumed via `consoleClient`/`consoleQuery`; AGENTS.md forbids handwritten REST helpers or direct edits to generated contracts. — [packages/contracts/package.json#L37-42](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/packages/contracts/package.json#L37-L42) [web/service/console/index.ts#L29-37](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/web/service/console/index.ts#L29-L37) [web/AGENTS.md](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/web/AGENTS.md)
- GraphEngine behavior is layered: callers pass `graph_engine_layers: Sequence[GraphEngineLayer]` (e.g. `DebugLoggingLayer`, `ExecutionLimitsLayer`, `ObservabilityLayer`, `PauseStatePersistenceLayer`) into generators/`WorkflowEntry`, so cross-cutting run behavior extends via engine layers rather than engine edits. — [api/core/app/apps/workflow/app_generator.py#L106-108](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/app/apps/workflow/app_generator.py#L106-L108) [api/core/workflow/workflow_entry.py#L42-43](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/workflow/workflow_entry.py#L42-L43) [api/core/app/layers/pause_state_persist_layer.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/app/layers/pause_state_persist_layer.py)
- Response-stream processing is a filter chain: `filter_graph_events` accepts additional `ResponseStreamFilter`-style filters (e.g. `HumanInputFormEventFilter`), letting new event semantics be inserted without touching engine internals. — [api/core/workflow/workflow_entry.py#L56-79](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/workflow/workflow_entry.py#L56-L79)
- New Flask extensions follow the `extensions/ext_*` convention: implement `init_app(app)` (optionally `is_enabled()`), add to the ordered list in `initialize_extensions` at the correct dependency position. — [api/app_factory.py#L175-259](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app_factory.py#L175-L259)

## Contributor handoff

This repo is agent-aware: layered AGENTS.md files (root, api/, web/, cli/, e2e/, dify-agent/) define per-area rules that must be read before editing. Backend work runs through `uv run --project api` and root `make` targets; frontend through pnpm/vp; CLI through pnpm with generated artifacts. Generated files (docker-compose.yaml, tree.generated.ts, contracts/generated, openapi specs) must never be hand-edited. The dominant correctness disciplines are tenant scoping, explicit bounded transactions, SSRF-routed egress, Pydantic v2 contracts at controller boundaries, and idempotent Celery tasks.
- Read the nearest AGENTS.md before editing: root `AGENTS.md` (repo gotchas: `uv run --project api`, integration tests CI-only, docker env layering), `api/AGENTS.md` (layer boundaries + typing rules), `web/AGENTS.md` (contracts/i18n/dify-ui + 'this is NOT the Next.js you know' warning), `cli/AGENTS.md` + `cli/ARD.md` (command structure), `e2e/AGENTS.md` (runtime ownership). — [AGENTS.md](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/AGENTS.md) [api/AGENTS.md](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/AGENTS.md) [web/AGENTS.md](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/web/AGENTS.md) [cli/AGENTS.md](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/cli/AGENTS.md) [e2e/AGENTS.md](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/e2e/AGENTS.md)
- Backend commands: `make lint` (ruff format+check --fix + `api-contract-lint` + `lint-imports` + `dotenv-linter`), `make type-check` (pyrefly + mypy), `make test` / `make test TARGET_TESTS=./api/tests/<path>`; direct Python via `uv run --project api`. — [Makefile#L68-120](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/Makefile#L68-L120) [api/AGENTS.md](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/AGENTS.md)
- Layer rule: transport parsing/serialization stays in controllers, orchestration in `services/`, domain policy in `core/` or its domain owner; `libs/` must stay business-agnostic. Pydantic v2 models for req/resp; domain exceptions are translated at the controller boundary (see `ChatApi.post`'s long except-chain). — [api/AGENTS.md](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/AGENTS.md) [api/controllers/web/completion.py#L219-263](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/web/completion.py#L219-L263)
- Never hand-edit generated artifacts: `docker/docker-compose.yaml` (edit `docker-compose-template.yaml`/`envs` + regenerate via `docker/generate_docker_compose`), `cli/src/commands/tree.generated.ts` (`pnpm tree:gen`), `packages/contracts/generated/*` (regen via `gen-api-contract`), and generated API specs. — [docker/docker-compose.yaml#L1-5](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/docker/docker-compose.yaml#L1-L5) [cli/src/commands/tree.generated.ts#L1-4](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/cli/src/commands/tree.generated.ts#L1-L4) [packages/contracts/package.json#L37-38](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/packages/contracts/package.json#L37-L38)
- Tenant safety is the #1 trap: scope every tenant-owned read/write by the complete owner chain, propagate `tenant_id` across layers, and rehydrate trusted entities from DB state after payload/async boundaries (pattern: `workflow_execute_task` reloads everything from `AppExecutionParams` ids). — [api/AGENTS.md](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/AGENTS.md) [api/tasks/app_generate/workflow_execute_task.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/tasks/app_generate/workflow_execute_task.py)
- Egress/storage/config discipline: read config only via `configs.dify_config`, storage only via `extensions.ext_storage.storage`, and outbound HTTP only via `core.helper.ssrf_proxy` (or `core.file.remote_fetcher` for remote files). — [api/AGENTS.md](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/AGENTS.md) [api/core/helper/ssrf_proxy.py#L1-13](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/core/helper/ssrf_proxy.py#L1-L13)
- Celery discipline: tasks that may retry must keep side effects idempotent and log affected resource ids; keep write transactions explicit/bounded and never do external I/O inside an open transaction unless a documented consistency contract requires it. — [api/AGENTS.md](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/AGENTS.md)
- Monkey-patching order is load-bearing: `app.py`'s `if __name__ == "__main__"` gevent patch must precede socket/lock imports; gunicorn (`gevent` worker class) and Celery (`celery_entrypoint.py`) do their own patching — adding imports above the patch block freezes the process. — [api/app.py#L3-21](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app.py#L3-L21) [api/celery_entrypoint.py#L1-13](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/celery_entrypoint.py#L1-L13) [api/gunicorn.conf.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/gunicorn.conf.py)
- `/system-features` is a minimal unauthenticated bootstrap allowlist, not a config registry — before touching controller schemas or `SystemFeatureModel`, read `controllers/API_SCHEMA_GUIDE.md`. — [api/AGENTS.md](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/AGENTS.md) [api/controllers/API_SCHEMA_GUIDE.md](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/API_SCHEMA_GUIDE.md)
- Frontend: use generated `consoleQuery`/`consoleClient` from `@/service/console` for backend calls (no handwritten REST/DTO mirrors); user strings must add keys in `web/i18n/locales/en-US/` and update every locale; prefer `@langgenius/dify-ui/*` primitives per the linked contracts in `web/AGENTS.md`. — [web/AGENTS.md](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/web/AGENTS.md) [web/service/console/index.ts](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/web/service/console/index.ts)
- Version is unified at 1.17.1 across `api/pyproject.toml`, `web/package.json`, `cli/package.json`, `dify-agent/pyproject.toml` and the compose image tags (`langgenius/dify-api:1.17.1`, `dify-web:1.17.1`, `dify-agent-backend:1.17.1`) — bump coherently. — [api/pyproject.toml#L1-4](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/pyproject.toml#L1-L4) [web/package.json#L2-3](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/web/package.json#L2-L3) [docker/docker-compose.yaml#L229-388](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/docker/docker-compose.yaml#L229-L388)
- Tests an agent can actually run locally: `make test` (unit only), `pnpm -C web test`/`vp test`, `pnpm -C cli test`, `cd dify-agent && make test`, `go test ./...` in dify-agent-runtime. Docker-backed integration, VDB smoke, and e2e are CI-only/opt-in — do not start long-running services during routine work. — [Makefile#L104-153](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/Makefile#L104-L153) [api/AGENTS.md](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/AGENTS.md) [cli/AGENTS.md](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/cli/AGENTS.md) [dify-agent/AGENTS.md](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/dify-agent/AGENTS.md)
- Error-surface convention: each blueprint namespace maps domain errors to HTTP errors in `controllers/<surface>/error.py` (e.g. `web/error.py`, `console/error.py` with `UnauthorizedAndForceLogout`); reuse these instead of raising raw werkzeug exceptions from services. — [api/controllers/web/completion.py#L13-24](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/web/completion.py#L13-L24) [api/controllers/console/error.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/console/error.py)
- Files an agent must read first for backend changes: `api/app_factory.py` (ext order + license gate), `api/configs/app_config.py` (settings pipeline), `api/services/app_generate_service.py` (primary dispatch), the target module's docstrings ('local contracts' per AGENTS.md), and `controllers/API_SCHEMA_GUIDE.md` when touching response schemas. (inferred) — [api/AGENTS.md](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/AGENTS.md) [api/app_factory.py](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/app_factory.py) [api/controllers/API_SCHEMA_GUIDE.md](https://github.com/langgenius/dify/blob/3a017e8cccb316f23f601af65d2e8ce92e68f788/api/controllers/API_SCHEMA_GUIDE.md)

Source repository: https://github.com/langgenius/dify
Full evidence corpus: https://github.com/Codevetter/codevetter/tree/main/benchmarks/repo-unpacks/dify.json

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
