diff --git a/Makefile b/Makefile
index e74a02db3..d190de3e6 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
# DeerFlow - Unified Development Environment
-.PHONY: help config config-upgrade check install dev dev-daemon start stop up down clean docker-init docker-start docker-stop docker-logs docker-logs-frontend docker-logs-gateway
+.PHONY: help config config-upgrade check install dev dev-pro dev-daemon dev-daemon-pro start start-pro start-daemon start-daemon-pro stop up up-pro down clean docker-init docker-start docker-start-pro docker-stop docker-logs docker-logs-frontend docker-logs-gateway
BASH ?= bash
@@ -20,18 +20,25 @@ help:
@echo " make install - Install all dependencies (frontend + backend)"
@echo " make setup-sandbox - Pre-pull sandbox container image (recommended)"
@echo " make dev - Start all services in development mode (with hot-reloading)"
- @echo " make dev-daemon - Start all services in background (daemon mode)"
+ @echo " make dev-pro - Start in dev + Gateway mode (experimental, no LangGraph server)"
+ @echo " make dev-daemon - Start dev services in background (daemon mode)"
+ @echo " make dev-daemon-pro - Start dev daemon + Gateway mode (experimental)"
@echo " make start - Start all services in production mode (optimized, no hot-reloading)"
+ @echo " make start-pro - Start in prod + Gateway mode (experimental)"
+ @echo " make start-daemon - Start prod services in background (daemon mode)"
+ @echo " make start-daemon-pro - Start prod daemon + Gateway mode (experimental)"
@echo " make stop - Stop all running services"
@echo " make clean - Clean up processes and temporary files"
@echo ""
@echo "Docker Production Commands:"
@echo " make up - Build and start production Docker services (localhost:2026)"
+ @echo " make up-pro - Build and start production Docker in Gateway mode (experimental)"
@echo " make down - Stop and remove production Docker containers"
@echo ""
@echo "Docker Development Commands:"
@echo " make docker-init - Pull the sandbox image"
@echo " make docker-start - Start Docker services (mode-aware from config.yaml, localhost:2026)"
+ @echo " make docker-start-pro - Start Docker in Gateway mode (experimental, no LangGraph container)"
@echo " make docker-stop - Stop Docker development services"
@echo " make docker-logs - View Docker development logs"
@echo " make docker-logs-frontend - View Docker frontend logs"
@@ -105,6 +112,15 @@ else
@./scripts/serve.sh --dev
endif
+# Start all services in dev + Gateway mode (experimental: agent runtime embedded in Gateway)
+dev-pro:
+ @$(PYTHON) ./scripts/check.py
+ifeq ($(OS),Windows_NT)
+ @call scripts\run-with-git-bash.cmd ./scripts/serve.sh --dev --gateway
+else
+ @./scripts/serve.sh --dev --gateway
+endif
+
# Start all services in production mode (with optimizations)
start:
@$(PYTHON) ./scripts/check.py
@@ -114,30 +130,54 @@ else
@./scripts/serve.sh --prod
endif
+# Start all services in prod + Gateway mode (experimental)
+start-pro:
+ @$(PYTHON) ./scripts/check.py
+ifeq ($(OS),Windows_NT)
+ @call scripts\run-with-git-bash.cmd ./scripts/serve.sh --prod --gateway
+else
+ @./scripts/serve.sh --prod --gateway
+endif
+
# Start all services in daemon mode (background)
dev-daemon:
@$(PYTHON) ./scripts/check.py
ifeq ($(OS),Windows_NT)
- @call scripts\run-with-git-bash.cmd ./scripts/start-daemon.sh
+ @call scripts\run-with-git-bash.cmd ./scripts/serve.sh --dev --daemon
else
- @./scripts/start-daemon.sh
+ @./scripts/serve.sh --dev --daemon
+endif
+
+# Start daemon + Gateway mode (experimental)
+dev-daemon-pro:
+ @$(PYTHON) ./scripts/check.py
+ifeq ($(OS),Windows_NT)
+ @call scripts\run-with-git-bash.cmd ./scripts/serve.sh --dev --gateway --daemon
+else
+ @./scripts/serve.sh --dev --gateway --daemon
+endif
+
+# Start prod services in daemon mode (background)
+start-daemon:
+ @$(PYTHON) ./scripts/check.py
+ifeq ($(OS),Windows_NT)
+ @call scripts\run-with-git-bash.cmd ./scripts/serve.sh --prod --daemon
+else
+ @./scripts/serve.sh --prod --daemon
+endif
+
+# Start prod daemon + Gateway mode (experimental)
+start-daemon-pro:
+ @$(PYTHON) ./scripts/check.py
+ifeq ($(OS),Windows_NT)
+ @call scripts\run-with-git-bash.cmd ./scripts/serve.sh --prod --gateway --daemon
+else
+ @./scripts/serve.sh --prod --gateway --daemon
endif
# Stop all services
stop:
- @echo "Stopping all services..."
- @-pkill -f "langgraph dev" 2>/dev/null || true
- @-pkill -f "uvicorn app.gateway.app:app" 2>/dev/null || true
- @-pkill -f "next dev" 2>/dev/null || true
- @-pkill -f "next start" 2>/dev/null || true
- @-pkill -f "next-server" 2>/dev/null || true
- @-pkill -f "next-server" 2>/dev/null || true
- @-nginx -c $(PWD)/docker/nginx/nginx.local.conf -p $(PWD) -s quit 2>/dev/null || true
- @sleep 1
- @-pkill -9 nginx 2>/dev/null || true
- @echo "Cleaning up sandbox containers..."
- @-./scripts/cleanup-containers.sh deer-flow-sandbox 2>/dev/null || true
- @echo "✓ All services stopped"
+ @./scripts/serve.sh --stop
# Clean up
clean: stop
@@ -159,6 +199,10 @@ docker-init:
docker-start:
@./scripts/docker.sh start
+# Start Docker in Gateway mode (experimental)
+docker-start-pro:
+ @./scripts/docker.sh start --gateway
+
# Stop Docker development environment
docker-stop:
@./scripts/docker.sh stop
@@ -181,6 +225,10 @@ docker-logs-gateway:
up:
@./scripts/deploy.sh
+# Build and start production services in Gateway mode
+up-pro:
+ @./scripts/deploy.sh --gateway
+
# Stop and remove production containers
down:
@./scripts/deploy.sh down
diff --git a/README.md b/README.md
index 1c1f6dcdf..14aec9fc6 100644
--- a/README.md
+++ b/README.md
@@ -280,6 +280,60 @@ On Windows, run the local development flow from Git Bash. Native `cmd.exe` and P
6. **Access**: http://localhost:2026
+#### Startup Modes
+
+DeerFlow supports multiple startup modes across two dimensions:
+
+- **Dev / Prod** — dev enables hot-reload; prod uses pre-built frontend
+- **Standard / Gateway** — standard uses a separate LangGraph server (4 processes); Gateway mode (experimental) embeds the agent runtime in the Gateway API (3 processes)
+
+| | **Local Foreground** | **Local Daemon** | **Docker Dev** | **Docker Prod** |
+|---|---|---|---|---|
+| **Dev** | `./scripts/serve.sh --dev`
`make dev` | `./scripts/serve.sh --dev --daemon`
`make dev-daemon` | `./scripts/docker.sh start`
`make docker-start` | — |
+| **Dev + Gateway** | `./scripts/serve.sh --dev --gateway`
`make dev-pro` | `./scripts/serve.sh --dev --gateway --daemon`
`make dev-daemon-pro` | `./scripts/docker.sh start --gateway`
`make docker-start-pro` | — |
+| **Prod** | `./scripts/serve.sh --prod`
`make start` | `./scripts/serve.sh --prod --daemon`
`make start-daemon` | — | `./scripts/deploy.sh`
`make up` |
+| **Prod + Gateway** | `./scripts/serve.sh --prod --gateway`
`make start-pro` | `./scripts/serve.sh --prod --gateway --daemon`
`make start-daemon-pro` | — | `./scripts/deploy.sh --gateway`
`make up-pro` |
+
+| Action | Local | Docker Dev | Docker Prod |
+|---|---|---|---|
+| **Stop** | `./scripts/serve.sh --stop`
`make stop` | `./scripts/docker.sh stop`
`make docker-stop` | `./scripts/deploy.sh down`
`make down` |
+| **Restart** | `./scripts/serve.sh --restart [flags]` | `./scripts/docker.sh restart` | — |
+
+> **Gateway mode** eliminates the LangGraph server process — the Gateway API handles agent execution directly via async tasks, managing its own concurrency.
+
+#### Why Gateway Mode?
+
+In standard mode, DeerFlow runs a dedicated [LangGraph Platform](https://langchain-ai.github.io/langgraph/) server alongside the Gateway API. This architecture works well but has trade-offs:
+
+| | Standard Mode | Gateway Mode |
+|---|---|---|
+| **Architecture** | Gateway (REST API) + LangGraph (agent runtime) | Gateway embeds agent runtime |
+| **Concurrency** | `--n-jobs-per-worker` per worker (requires license) | `--workers` × async tasks (no per-worker cap) |
+| **Containers / Processes** | 4 (frontend, gateway, langgraph, nginx) | 3 (frontend, gateway, nginx) |
+| **Resource usage** | Higher (two Python runtimes) | Lower (single Python runtime) |
+| **LangGraph Platform license** | Required for production images | Not required |
+| **Cold start** | Slower (two services to initialize) | Faster |
+
+Both modes are functionally equivalent — the same agents, tools, and skills work in either mode.
+
+#### Docker Production Deployment
+
+`deploy.sh` supports building and starting separately. Images are mode-agnostic — runtime mode is selected at start time:
+
+```bash
+# One-step (build + start)
+deploy.sh # standard mode (default)
+deploy.sh --gateway # gateway mode
+
+# Two-step (build once, start with any mode)
+deploy.sh build # build all images
+deploy.sh start # start in standard mode
+deploy.sh start --gateway # start in gateway mode
+
+# Stop
+deploy.sh down
+```
+
### Advanced
#### Sandbox Mode
diff --git a/backend/CLAUDE.md b/backend/CLAUDE.md
index a45b14253..846429e40 100644
--- a/backend/CLAUDE.md
+++ b/backend/CLAUDE.md
@@ -13,6 +13,10 @@ DeerFlow is a LangGraph-based AI super agent system with a full-stack architectu
- **Nginx** (port 2026): Unified reverse proxy entry point
- **Provisioner** (port 8002, optional in Docker dev): Started only when sandbox is configured for provisioner/Kubernetes mode
+**Runtime Modes**:
+- **Standard mode** (`make dev`): LangGraph Server handles agent execution as a separate process. 4 processes total.
+- **Gateway mode** (`make dev-pro`, experimental): Agent runtime embedded in Gateway via `RunManager` + `run_agent()` + `StreamBridge` (`packages/harness/deerflow/runtime/`). Service manages its own concurrency via async tasks. 3 processes total, no LangGraph Server.
+
**Project Structure**:
```
deer-flow/
@@ -80,6 +84,8 @@ When making code changes, you MUST update the relevant documentation:
make check # Check system requirements
make install # Install all dependencies (frontend + backend)
make dev # Start all services (LangGraph + Gateway + Frontend + Nginx), with config.yaml preflight
+make dev-pro # Gateway mode (experimental): skip LangGraph, agent runtime embedded in Gateway
+make start-pro # Production + Gateway mode (experimental)
make stop # Stop all services
```
@@ -436,8 +442,25 @@ make dev
This starts all services and makes the application available at `http://localhost:2026`.
+**All startup modes:**
+
+| | **Local Foreground** | **Local Daemon** | **Docker Dev** | **Docker Prod** |
+|---|---|---|---|---|
+| **Dev** | `./scripts/serve.sh --dev`
`make dev` | `./scripts/serve.sh --dev --daemon`
`make dev-daemon` | `./scripts/docker.sh start`
`make docker-start` | — |
+| **Dev + Gateway** | `./scripts/serve.sh --dev --gateway`
`make dev-pro` | `./scripts/serve.sh --dev --gateway --daemon`
`make dev-daemon-pro` | `./scripts/docker.sh start --gateway`
`make docker-start-pro` | — |
+| **Prod** | `./scripts/serve.sh --prod`
`make start` | `./scripts/serve.sh --prod --daemon`
`make start-daemon` | — | `./scripts/deploy.sh`
`make up` |
+| **Prod + Gateway** | `./scripts/serve.sh --prod --gateway`
`make start-pro` | `./scripts/serve.sh --prod --gateway --daemon`
`make start-daemon-pro` | — | `./scripts/deploy.sh --gateway`
`make up-pro` |
+
+| Action | Local | Docker Dev | Docker Prod |
+|---|---|---|---|
+| **Stop** | `./scripts/serve.sh --stop`
`make stop` | `./scripts/docker.sh stop`
`make docker-stop` | `./scripts/deploy.sh down`
`make down` |
+| **Restart** | `./scripts/serve.sh --restart [flags]` | `./scripts/docker.sh restart` | — |
+
+Gateway mode embeds the agent runtime in Gateway, no LangGraph server.
+
**Nginx routing**:
-- `/api/langgraph/*` → LangGraph Server (2024)
+- Standard mode: `/api/langgraph/*` → LangGraph Server (2024)
+- Gateway mode: `/api/langgraph/*` → Gateway embedded runtime (8001) (via envsubst)
- `/api/*` (other) → Gateway API (8001)
- `/` (non-API) → Frontend (3000)
diff --git a/docker/docker-compose-dev.yaml b/docker/docker-compose-dev.yaml
index c0749ba9d..53dcb80b2 100644
--- a/docker/docker-compose-dev.yaml
+++ b/docker/docker-compose-dev.yaml
@@ -19,8 +19,6 @@ services:
# cluster via the K8s API.
# Backend accesses sandboxes directly via host.docker.internal:{NodePort}.
provisioner:
- profiles:
- - provisioner
build:
context: ./provisioner
dockerfile: Dockerfile
@@ -59,20 +57,25 @@ services:
# ── Reverse Proxy ──────────────────────────────────────────────────────
# Routes API traffic to gateway/langgraph and (optionally) provisioner.
- # Select nginx config via NGINX_CONF:
- # - nginx.local.conf (default): no provisioner route (local/aio modes)
- # - nginx.conf: includes provisioner route (provisioner mode)
+ # LANGGRAPH_UPSTREAM and LANGGRAPH_REWRITE control gateway vs standard
+ # routing (processed by envsubst at container start).
nginx:
image: nginx:alpine
container_name: deer-flow-nginx
ports:
- "2026:2026"
volumes:
- - ./nginx/${NGINX_CONF:-nginx.conf}:/etc/nginx/nginx.conf:ro
+ - ./nginx/nginx.conf:/etc/nginx/nginx.conf.template:ro
+ environment:
+ - LANGGRAPH_UPSTREAM=${LANGGRAPH_UPSTREAM:-langgraph:2024}
+ - LANGGRAPH_REWRITE=${LANGGRAPH_REWRITE:-/}
+ command: >
+ sh -c "envsubst '$$LANGGRAPH_UPSTREAM $$LANGGRAPH_REWRITE'
+ < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
+ && nginx -g 'daemon off;'"
depends_on:
- frontend
- gateway
- - langgraph
networks:
- deer-flow-dev
restart: unless-stopped
diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml
index 98d549878..8c1432da7 100644
--- a/docker/docker-compose.yaml
+++ b/docker/docker-compose.yaml
@@ -29,11 +29,17 @@ services:
ports:
- "${PORT:-2026}:2026"
volumes:
- - ./nginx/${NGINX_CONF:-nginx.conf}:/etc/nginx/nginx.conf:ro
+ - ./nginx/nginx.conf:/etc/nginx/nginx.conf.template:ro
+ environment:
+ - LANGGRAPH_UPSTREAM=${LANGGRAPH_UPSTREAM:-langgraph:2024}
+ - LANGGRAPH_REWRITE=${LANGGRAPH_REWRITE:-/}
+ command: >
+ sh -c "envsubst '$$LANGGRAPH_UPSTREAM $$LANGGRAPH_REWRITE'
+ < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
+ && nginx -g 'daemon off;'"
depends_on:
- frontend
- gateway
- - langgraph
networks:
- deer-flow
restart: unless-stopped
@@ -68,7 +74,7 @@ services:
UV_IMAGE: ${UV_IMAGE:-ghcr.io/astral-sh/uv:0.7.20}
UV_INDEX_URL: ${UV_INDEX_URL:-https://pypi.org/simple}
container_name: deer-flow-gateway
- command: sh -c "cd backend && PYTHONPATH=. uv run uvicorn app.gateway.app:app --host 0.0.0.0 --port 8001 --workers 2"
+ command: sh -c "cd backend && PYTHONPATH=. uv run uvicorn app.gateway.app:app --host 0.0.0.0 --port 8001 --workers ${GATEWAY_WORKERS:-4}"
volumes:
- ${DEER_FLOW_CONFIG_PATH}:/app/backend/config.yaml:ro
- ${DEER_FLOW_EXTENSIONS_CONFIG_PATH}:/app/backend/extensions_config.json:ro
@@ -160,13 +166,12 @@ services:
# ── Sandbox Provisioner (optional, Kubernetes mode) ────────────────────────
provisioner:
- profiles:
- - provisioner
build:
context: ./provisioner
dockerfile: Dockerfile
args:
APT_MIRROR: ${APT_MIRROR:-}
+ PIP_INDEX_URL: ${PIP_INDEX_URL:-}
container_name: deer-flow-provisioner
volumes:
- ~/.kube/config:/root/.kube/config:ro
diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf
index da570f709..c9a7be32b 100644
--- a/docker/nginx/nginx.conf
+++ b/docker/nginx/nginx.conf
@@ -27,7 +27,7 @@ http {
}
upstream langgraph {
- server langgraph:2024;
+ server ${LANGGRAPH_UPSTREAM};
}
upstream frontend {
@@ -57,9 +57,11 @@ http {
}
# LangGraph API routes
- # Rewrites /api/langgraph/* to /* before proxying
+ # In standard mode: /api/langgraph/* → langgraph:2024 (rewrite to /*)
+ # In gateway mode: /api/langgraph/* → gateway:8001 (rewrite to /api/*)
+ # Controlled by LANGGRAPH_UPSTREAM and LANGGRAPH_REWRITE env vars.
location /api/langgraph/ {
- rewrite ^/api/langgraph/(.*) /$1 break;
+ rewrite ^/api/langgraph/(.*) ${LANGGRAPH_REWRITE}$1 break;
proxy_pass http://langgraph;
proxy_http_version 1.1;
@@ -84,34 +86,6 @@ http {
chunked_transfer_encoding on;
}
- # Experimental: Gateway-backed LangGraph-compatible API
- # Frontend can opt-in via NEXT_PUBLIC_LANGGRAPH_BASE_URL=/api/langgraph-compat
- location /api/langgraph-compat/ {
- rewrite ^/api/langgraph-compat/(.*) /api/$1 break;
- proxy_pass http://gateway;
- proxy_http_version 1.1;
-
- # Headers
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto $scheme;
- proxy_set_header Connection '';
-
- # SSE/Streaming support
- proxy_buffering off;
- proxy_cache off;
- proxy_set_header X-Accel-Buffering no;
-
- # Timeouts for long-running requests
- proxy_connect_timeout 600s;
- proxy_send_timeout 600s;
- proxy_read_timeout 600s;
-
- # Chunked transfer encoding
- chunked_transfer_encoding on;
- }
-
# Custom API: Models endpoint
location /api/models {
proxy_pass http://gateway;
diff --git a/docker/provisioner/Dockerfile b/docker/provisioner/Dockerfile
index 6f30e778d..96ef93156 100644
--- a/docker/provisioner/Dockerfile
+++ b/docker/provisioner/Dockerfile
@@ -1,6 +1,7 @@
FROM python:3.12-slim-bookworm
ARG APT_MIRROR
+ARG PIP_INDEX_URL
# Optionally override apt mirror for restricted networks (e.g. APT_MIRROR=mirrors.aliyun.com)
RUN if [ -n "${APT_MIRROR}" ]; then \
@@ -15,6 +16,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
# Install Python dependencies
RUN pip install --no-cache-dir \
+ ${PIP_INDEX_URL:+--index-url "$PIP_INDEX_URL"} \
fastapi \
"uvicorn[standard]" \
kubernetes
diff --git a/scripts/deploy.sh b/scripts/deploy.sh
index 66df240ef..26cb3bc29 100755
--- a/scripts/deploy.sh
+++ b/scripts/deploy.sh
@@ -1,16 +1,57 @@
#!/usr/bin/env bash
#
-# deploy.sh - Build and start (or stop) DeerFlow production services
+# deploy.sh - Build, start, or stop DeerFlow production services
#
-# Usage:
-# deploy.sh [up] — build images and start containers (default)
-# deploy.sh down — stop and remove containers
+# Commands:
+# deploy.sh [--MODE] — build + start (default: --standard)
+# deploy.sh build — build all images (mode-agnostic)
+# deploy.sh start [--MODE] — start from pre-built images (default: --standard)
+# deploy.sh down — stop and remove containers
+#
+# Runtime modes:
+# --standard (default) All services including LangGraph server.
+# --gateway No LangGraph container; nginx routes /api/langgraph/*
+# to the Gateway compat API instead.
+#
+# Sandbox mode (local / aio / provisioner) is auto-detected from config.yaml.
+#
+# Examples:
+# deploy.sh # build + start in standard mode
+# deploy.sh --gateway # build + start in gateway mode
+# deploy.sh build # build all images
+# deploy.sh start --gateway # start pre-built images in gateway mode
+# deploy.sh down # stop and remove containers
#
# Must be run from the repo root directory.
set -e
-CMD="${1:-up}"
+RUNTIME_MODE="standard"
+
+case "${1:-}" in
+ build|start|down)
+ CMD="$1"
+ if [ -n "${2:-}" ]; then
+ case "$2" in
+ --standard) RUNTIME_MODE="standard" ;;
+ --gateway) RUNTIME_MODE="gateway" ;;
+ *) echo "Unknown mode: $2"; echo "Usage: deploy.sh [build|start|down] [--standard|--gateway]"; exit 1 ;;
+ esac
+ fi
+ ;;
+ --standard|--gateway)
+ CMD=""
+ RUNTIME_MODE="${1#--}"
+ ;;
+ "")
+ CMD=""
+ ;;
+ *)
+ echo "Unknown argument: $1"
+ echo "Usage: deploy.sh [build|start|down] [--standard|--gateway]"
+ exit 1
+ ;;
+esac
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"
@@ -150,6 +191,32 @@ if [ "$CMD" = "down" ]; then
exit 0
fi
+# ── build ────────────────────────────────────────────────────────────────────
+# Build produces mode-agnostic images. No --gateway or sandbox detection needed.
+
+if [ "$CMD" = "build" ]; then
+ echo "=========================================="
+ echo " DeerFlow — Building Images"
+ echo "=========================================="
+ echo ""
+
+ # Docker socket is needed for compose to parse volume specs
+ if [ -z "$DEER_FLOW_DOCKER_SOCKET" ]; then
+ export DEER_FLOW_DOCKER_SOCKET="/var/run/docker.sock"
+ fi
+
+ "${COMPOSE_CMD[@]}" build
+
+ echo ""
+ echo "=========================================="
+ echo " ✓ Images built successfully"
+ echo "=========================================="
+ echo ""
+ echo " Next: deploy.sh start [--gateway]"
+ echo ""
+ exit 0
+fi
+
# ── Banner ────────────────────────────────────────────────────────────────────
echo "=========================================="
@@ -157,19 +224,28 @@ echo " DeerFlow Production Deployment"
echo "=========================================="
echo ""
-# ── Step 1: Detect sandbox mode ──────────────────────────────────────────────
+# ── Detect runtime configuration ────────────────────────────────────────────
+# Only needed for start / up — determines which containers to launch.
sandbox_mode="$(detect_sandbox_mode)"
echo -e "${BLUE}Sandbox mode: $sandbox_mode${NC}"
-if [ "$sandbox_mode" = "provisioner" ]; then
- services=""
- extra_args="--profile provisioner"
-else
- services="frontend gateway langgraph nginx"
- extra_args=""
-fi
+echo -e "${BLUE}Runtime mode: $RUNTIME_MODE${NC}"
+case "$RUNTIME_MODE" in
+ gateway)
+ export LANGGRAPH_UPSTREAM=gateway:8001
+ export LANGGRAPH_REWRITE=/api/
+ services="frontend gateway nginx"
+ ;;
+ standard)
+ services="frontend gateway langgraph nginx"
+ ;;
+esac
+
+if [ "$sandbox_mode" = "provisioner" ]; then
+ services="$services provisioner"
+fi
# ── DEER_FLOW_DOCKER_SOCKET ───────────────────────────────────────────────────
@@ -189,22 +265,34 @@ fi
echo ""
-# ── Step 2: Build and start ───────────────────────────────────────────────────
+# ── Start / Up ───────────────────────────────────────────────────────────────
-echo "Building images and starting containers..."
-echo ""
-
-# shellcheck disable=SC2086
-"${COMPOSE_CMD[@]}" $extra_args up --build -d --remove-orphans $services
+if [ "$CMD" = "start" ]; then
+ echo "Starting containers (no rebuild)..."
+ echo ""
+ # shellcheck disable=SC2086
+ "${COMPOSE_CMD[@]}" up -d --remove-orphans $services
+else
+ # Default: build + start
+ echo "Building images and starting containers..."
+ echo ""
+ # shellcheck disable=SC2086
+ "${COMPOSE_CMD[@]}" up --build -d --remove-orphans $services
+fi
echo ""
echo "=========================================="
-echo " DeerFlow is running!"
+echo " DeerFlow is running! ($RUNTIME_MODE mode)"
echo "=========================================="
echo ""
echo " 🌐 Application: http://localhost:${PORT:-2026}"
echo " 📡 API Gateway: http://localhost:${PORT:-2026}/api/*"
-echo " 🤖 LangGraph: http://localhost:${PORT:-2026}/api/langgraph/*"
+if [ "$RUNTIME_MODE" = "gateway" ]; then
+ echo " 🤖 Runtime: Gateway embedded"
+ echo " API: /api/langgraph/* → Gateway (compat)"
+else
+ echo " 🤖 LangGraph: http://localhost:${PORT:-2026}/api/langgraph/*"
+fi
echo ""
echo " Manage:"
echo " make down — stop and remove containers"
diff --git a/scripts/docker.sh b/scripts/docker.sh
index bc20d4177..0ef1896fe 100755
--- a/scripts/docker.sh
+++ b/scripts/docker.sh
@@ -148,9 +148,18 @@ init() {
}
# Start Docker development environment
+# Usage: start [--gateway]
start() {
local sandbox_mode
local services
+ local gateway_mode=false
+
+ # Check for --gateway flag
+ for arg in "$@"; do
+ if [ "$arg" = "--gateway" ]; then
+ gateway_mode=true
+ fi
+ done
echo "=========================================="
echo " Starting DeerFlow Docker Development"
@@ -159,12 +168,21 @@ start() {
sandbox_mode="$(detect_sandbox_mode)"
- if [ "$sandbox_mode" = "provisioner" ]; then
- services="frontend gateway langgraph provisioner nginx"
+ if $gateway_mode; then
+ services="frontend gateway nginx"
+ if [ "$sandbox_mode" = "provisioner" ]; then
+ services="frontend gateway provisioner nginx"
+ fi
else
services="frontend gateway langgraph nginx"
+ if [ "$sandbox_mode" = "provisioner" ]; then
+ services="frontend gateway langgraph provisioner nginx"
+ fi
fi
+ if $gateway_mode; then
+ echo -e "${BLUE}Runtime: Gateway mode (experimental) — no LangGraph container${NC}"
+ fi
echo -e "${BLUE}Detected sandbox mode: $sandbox_mode${NC}"
if [ "$sandbox_mode" = "provisioner" ]; then
echo -e "${BLUE}Provisioner enabled (Kubernetes mode).${NC}"
@@ -213,6 +231,12 @@ start() {
fi
fi
+ # Set nginx routing for gateway mode (envsubst in nginx container)
+ if $gateway_mode; then
+ export LANGGRAPH_UPSTREAM=gateway:8001
+ export LANGGRAPH_REWRITE=/api/
+ fi
+
echo "Building and starting containers..."
cd "$DOCKER_DIR" && $COMPOSE_CMD up --build -d --remove-orphans $services
echo ""
@@ -222,7 +246,12 @@ start() {
echo ""
echo " 🌐 Application: http://localhost:2026"
echo " 📡 API Gateway: http://localhost:2026/api/*"
- echo " 🤖 LangGraph: http://localhost:2026/api/langgraph/*"
+ if $gateway_mode; then
+ echo " 🤖 Runtime: Gateway embedded"
+ echo " API: /api/langgraph/* → Gateway (compat)"
+ else
+ echo " 🤖 LangGraph: http://localhost:2026/api/langgraph/*"
+ fi
echo ""
echo " 📋 View logs: make docker-logs"
echo " 🛑 Stop: make docker-stop"
@@ -300,9 +329,10 @@ help() {
echo "Usage: $0 [options]"
echo ""
echo "Commands:"
- echo " init - Pull the sandbox image (speeds up first Pod startup)"
- echo " start - Start Docker services (auto-detects sandbox mode from config.yaml)"
- echo " restart - Restart all running Docker services"
+ echo " init - Pull the sandbox image (speeds up first Pod startup)"
+ echo " start - Start Docker services (auto-detects sandbox mode from config.yaml)"
+ echo " start --gateway - Start without LangGraph container (Gateway mode, experimental)"
+ echo " restart - Restart all running Docker services"
echo " logs [option] - View Docker development logs"
echo " --frontend View frontend logs only"
echo " --gateway View gateway logs only"
@@ -320,7 +350,8 @@ main() {
init
;;
start)
- start
+ shift
+ start "$@"
;;
restart)
restart
diff --git a/scripts/serve.sh b/scripts/serve.sh
index fe1556a91..bd810e05e 100755
--- a/scripts/serve.sh
+++ b/scripts/serve.sh
@@ -1,15 +1,40 @@
#!/usr/bin/env bash
#
-# start.sh - Start all DeerFlow development services
+# serve.sh — Unified DeerFlow service launcher
+#
+# Usage:
+# ./scripts/serve.sh [--dev|--prod] [--gateway] [--daemon] [--stop|--restart]
+#
+# Modes:
+# --dev Development mode with hot-reload (default)
+# --prod Production mode, pre-built frontend, no hot-reload
+# --gateway Gateway mode (experimental): skip LangGraph server,
+# agent runtime embedded in Gateway API
+# --daemon Run all services in background (nohup), exit after startup
+#
+# Actions:
+# --skip-install Skip dependency installation (faster restart)
+# --stop Stop all running services and exit
+# --restart Stop all services, then start with the given mode flags
+#
+# Examples:
+# ./scripts/serve.sh --dev # Standard dev (4 processes)
+# ./scripts/serve.sh --dev --gateway # Gateway dev (3 processes)
+# ./scripts/serve.sh --prod --gateway # Gateway prod (3 processes)
+# ./scripts/serve.sh --dev --daemon # Standard dev, background
+# ./scripts/serve.sh --dev --gateway --daemon # Gateway dev, background
+# ./scripts/serve.sh --stop # Stop all services
+# ./scripts/serve.sh --restart --dev --gateway # Restart in gateway mode
#
# Must be run from the repo root directory.
set -e
-REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+REPO_ROOT="$(builtin cd "$(dirname "${BASH_SOURCE[0]}")/.." >/dev/null 2>&1 && pwd -P)"
cd "$REPO_ROOT"
-# ── Load environment variables from .env ──────────────────────────────────────
+# ── Load .env ────────────────────────────────────────────────────────────────
+
if [ -f "$REPO_ROOT/.env" ]; then
set -a
source "$REPO_ROOT/.env"
@@ -19,14 +44,80 @@ fi
# ── Argument parsing ─────────────────────────────────────────────────────────
DEV_MODE=true
+GATEWAY_MODE=false
+DAEMON_MODE=false
+SKIP_INSTALL=false
+ACTION="start" # start | stop | restart
+
for arg in "$@"; do
case "$arg" in
- --dev) DEV_MODE=true ;;
- --prod) DEV_MODE=false ;;
- *) echo "Unknown argument: $arg"; echo "Usage: $0 [--dev|--prod]"; exit 1 ;;
+ --dev) DEV_MODE=true ;;
+ --prod) DEV_MODE=false ;;
+ --gateway) GATEWAY_MODE=true ;;
+ --daemon) DAEMON_MODE=true ;;
+ --skip-install) SKIP_INSTALL=true ;;
+ --stop) ACTION="stop" ;;
+ --restart) ACTION="restart" ;;
+ *)
+ echo "Unknown argument: $arg"
+ echo "Usage: $0 [--dev|--prod] [--gateway] [--daemon] [--skip-install] [--stop|--restart]"
+ exit 1
+ ;;
esac
done
+# ── Stop helper ──────────────────────────────────────────────────────────────
+
+stop_all() {
+ echo "Stopping all services..."
+ pkill -f "langgraph dev" 2>/dev/null || true
+ pkill -f "uvicorn app.gateway.app:app" 2>/dev/null || true
+ pkill -f "next dev" 2>/dev/null || true
+ pkill -f "next start" 2>/dev/null || true
+ pkill -f "next-server" 2>/dev/null || true
+ nginx -c "$REPO_ROOT/docker/nginx/nginx.local.conf" -p "$REPO_ROOT" -s quit 2>/dev/null || true
+ sleep 1
+ pkill -9 nginx 2>/dev/null || true
+ ./scripts/cleanup-containers.sh deer-flow-sandbox 2>/dev/null || true
+ echo "✓ All services stopped"
+}
+
+# ── Action routing ───────────────────────────────────────────────────────────
+
+if [ "$ACTION" = "stop" ]; then
+ stop_all
+ exit 0
+fi
+
+ALREADY_STOPPED=false
+if [ "$ACTION" = "restart" ]; then
+ stop_all
+ sleep 1
+ ALREADY_STOPPED=true
+fi
+
+# ── Derive runtime flags ────────────────────────────────────────────────────
+
+if $GATEWAY_MODE; then
+ export SKIP_LANGGRAPH_SERVER=1
+fi
+
+# Mode label for banner
+if $DEV_MODE && $GATEWAY_MODE; then
+ MODE_LABEL="DEV + GATEWAY (experimental)"
+elif $DEV_MODE; then
+ MODE_LABEL="DEV (hot-reload enabled)"
+elif $GATEWAY_MODE; then
+ MODE_LABEL="PROD + GATEWAY (experimental)"
+else
+ MODE_LABEL="PROD (optimized)"
+fi
+
+if $DAEMON_MODE; then
+ MODE_LABEL="$MODE_LABEL [daemon]"
+fi
+
+# Frontend command
if $DEV_MODE; then
FRONTEND_CMD="pnpm run dev"
else
@@ -35,46 +126,26 @@ else
elif command -v python >/dev/null 2>&1; then
PYTHON_BIN="python"
else
- echo "Python is required to generate BETTER_AUTH_SECRET, but neither python3 nor python was found."
+ echo "Python is required to generate BETTER_AUTH_SECRET."
exit 1
fi
FRONTEND_CMD="env BETTER_AUTH_SECRET=$($PYTHON_BIN -c 'import secrets; print(secrets.token_hex(16))') pnpm run preview"
fi
-# ── Stop existing services ────────────────────────────────────────────────────
-
-echo "Stopping existing services if any..."
-pkill -f "langgraph dev" 2>/dev/null || true
-pkill -f "uvicorn app.gateway.app:app" 2>/dev/null || true
-pkill -f "next dev" 2>/dev/null || true
-pkill -f "next-server" 2>/dev/null || true
-nginx -c "$REPO_ROOT/docker/nginx/nginx.local.conf" -p "$REPO_ROOT" -s quit 2>/dev/null || true
-sleep 1
-pkill -9 nginx 2>/dev/null || true
-killall -9 nginx 2>/dev/null || true
-./scripts/cleanup-containers.sh deer-flow-sandbox 2>/dev/null || true
-sleep 1
-
-# ── Banner ────────────────────────────────────────────────────────────────────
-
-echo ""
-echo "=========================================="
-echo " Starting DeerFlow Development Server"
-echo "=========================================="
-echo ""
-if $DEV_MODE; then
- echo " Mode: DEV (hot-reload enabled)"
- echo " Tip: run \`make start\` in production mode"
+# Extra flags for uvicorn/langgraph
+LANGGRAPH_EXTRA_FLAGS="--no-reload"
+if $DEV_MODE && ! $DAEMON_MODE; then
+ GATEWAY_EXTRA_FLAGS="--reload --reload-include='*.yaml' --reload-include='.env' --reload-exclude='*.pyc' --reload-exclude='__pycache__' --reload-exclude='sandbox/' --reload-exclude='.deer-flow/'"
else
- echo " Mode: PROD (hot-reload disabled)"
- echo " Tip: run \`make dev\` to start in development mode"
+ GATEWAY_EXTRA_FLAGS=""
+fi
+
+# ── Stop existing services (skip if restart already did it) ──────────────────
+
+if ! $ALREADY_STOPPED; then
+ stop_all
+ sleep 1
fi
-echo ""
-echo "Services starting up..."
-echo " → Backend: LangGraph + Gateway"
-echo " → Frontend: Next.js"
-echo " → Nginx: Reverse Proxy"
-echo ""
# ── Config check ─────────────────────────────────────────────────────────────
@@ -84,64 +155,108 @@ if ! { \
[ -f config.yaml ]; \
}; then
echo "✗ No DeerFlow config file found."
- echo " Checked these locations:"
- echo " - $DEER_FLOW_CONFIG_PATH (when DEER_FLOW_CONFIG_PATH is set)"
- echo " - backend/config.yaml"
- echo " - ./config.yaml"
- echo ""
- echo " Run 'make config' from the repo root to generate ./config.yaml, then set required model API keys in .env or your config file."
+ echo " Run 'make config' to generate config.yaml."
exit 1
fi
-# ── Auto-upgrade config ──────────────────────────────────────────────────
-
"$REPO_ROOT/scripts/config-upgrade.sh"
-# ── Cleanup trap ─────────────────────────────────────────────────────────────
+# ── Install dependencies ────────────────────────────────────────────────────
+
+if ! $SKIP_INSTALL; then
+ echo "Syncing dependencies..."
+ (cd backend && uv sync --quiet) || { echo "✗ Backend dependency install failed"; exit 1; }
+ (cd frontend && pnpm install --silent) || { echo "✗ Frontend dependency install failed"; exit 1; }
+ echo "✓ Dependencies synced"
+else
+ echo "⏩ Skipping dependency install (--skip-install)"
+fi
+
+# ── Sync frontend .env.local ─────────────────────────────────────────────────
+# Next.js .env.local takes precedence over process env vars.
+# The script manages the NEXT_PUBLIC_LANGGRAPH_BASE_URL line to ensure
+# the frontend routes match the active backend mode.
+
+FRONTEND_ENV_LOCAL="$REPO_ROOT/frontend/.env.local"
+ENV_KEY="NEXT_PUBLIC_LANGGRAPH_BASE_URL"
+
+sync_frontend_env() {
+ if $GATEWAY_MODE; then
+ # Point frontend to Gateway's compat API
+ if [ -f "$FRONTEND_ENV_LOCAL" ] && grep -q "^${ENV_KEY}=" "$FRONTEND_ENV_LOCAL"; then
+ sed -i.bak "s|^${ENV_KEY}=.*|${ENV_KEY}=/api/langgraph-compat|" "$FRONTEND_ENV_LOCAL" && rm -f "${FRONTEND_ENV_LOCAL}.bak"
+ else
+ echo "${ENV_KEY}=/api/langgraph-compat" >> "$FRONTEND_ENV_LOCAL"
+ fi
+ else
+ # Remove override — frontend falls back to /api/langgraph (standard)
+ if [ -f "$FRONTEND_ENV_LOCAL" ] && grep -q "^${ENV_KEY}=" "$FRONTEND_ENV_LOCAL"; then
+ sed -i.bak "/^${ENV_KEY}=/d" "$FRONTEND_ENV_LOCAL" && rm -f "${FRONTEND_ENV_LOCAL}.bak"
+ fi
+ fi
+}
+
+sync_frontend_env
+
+# ── Banner ───────────────────────────────────────────────────────────────────
+
+echo ""
+echo "=========================================="
+echo " Starting DeerFlow"
+echo "=========================================="
+echo ""
+echo " Mode: $MODE_LABEL"
+echo ""
+echo " Services:"
+if ! $GATEWAY_MODE; then
+ echo " LangGraph → localhost:2024 (agent runtime)"
+fi
+echo " Gateway → localhost:8001 (REST API$(if $GATEWAY_MODE; then echo " + agent runtime"; fi))"
+echo " Frontend → localhost:3000 (Next.js)"
+echo " Nginx → localhost:2026 (reverse proxy)"
+echo ""
+
+# ── Cleanup handler ──────────────────────────────────────────────────────────
cleanup() {
trap - INT TERM
echo ""
- echo "Shutting down services..."
- if [ "${SKIP_LANGGRAPH_SERVER:-0}" != "1" ]; then
- pkill -f "langgraph dev" 2>/dev/null || true
- fi
- pkill -f "uvicorn app.gateway.app:app" 2>/dev/null || true
- pkill -f "next dev" 2>/dev/null || true
- pkill -f "next start" 2>/dev/null || true
- pkill -f "next-server" 2>/dev/null || true
- # Kill nginx using the captured PID first (most reliable),
- # then fall back to pkill/killall for any stray nginx workers.
- if [ -n "${NGINX_PID:-}" ] && kill -0 "$NGINX_PID" 2>/dev/null; then
- kill -TERM "$NGINX_PID" 2>/dev/null || true
- sleep 1
- kill -9 "$NGINX_PID" 2>/dev/null || true
- fi
- pkill -9 nginx 2>/dev/null || true
- killall -9 nginx 2>/dev/null || true
- echo "Cleaning up sandbox containers..."
- ./scripts/cleanup-containers.sh deer-flow-sandbox 2>/dev/null || true
- echo "✓ All services stopped"
+ stop_all
exit 0
}
+
trap cleanup INT TERM
-# ── Start services ────────────────────────────────────────────────────────────
+# ── Helper: start a service ──────────────────────────────────────────────────
+
+# run_service NAME COMMAND PORT TIMEOUT
+# In daemon mode, wraps with nohup. Waits for port to be ready.
+run_service() {
+ local name="$1" cmd="$2" port="$3" timeout="$4"
+
+ echo "Starting $name..."
+ if $DAEMON_MODE; then
+ nohup sh -c "$cmd" > /dev/null 2>&1 &
+ else
+ sh -c "$cmd" &
+ fi
+
+ ./scripts/wait-for-port.sh "$port" "$timeout" "$name" || {
+ local logfile="logs/$(echo "$name" | tr '[:upper:]' '[:lower:]' | tr ' ' '-').log"
+ echo "✗ $name failed to start."
+ [ -f "$logfile" ] && tail -20 "$logfile"
+ cleanup
+ }
+ echo "✓ $name started on localhost:$port"
+}
+
+# ── Start services ───────────────────────────────────────────────────────────
mkdir -p logs
mkdir -p temp/client_body_temp temp/proxy_temp temp/fastcgi_temp temp/uwsgi_temp temp/scgi_temp
-if $DEV_MODE; then
- LANGGRAPH_EXTRA_FLAGS="--no-reload"
- GATEWAY_EXTRA_FLAGS="--reload --reload-include='*.yaml' --reload-include='.env' --reload-exclude='*.pyc' --reload-exclude='__pycache__' --reload-exclude='sandbox/' --reload-exclude='.deer-flow/'"
-else
- LANGGRAPH_EXTRA_FLAGS="--no-reload"
- GATEWAY_EXTRA_FLAGS=""
-fi
-
-if [ "${SKIP_LANGGRAPH_SERVER:-0}" != "1" ]; then
- echo "Starting LangGraph server..."
- # Read log_level from config.yaml, fallback to env var, then to "info"
+# 1. LangGraph (skip in gateway mode)
+if ! $GATEWAY_MODE; then
CONFIG_LOG_LEVEL=$(grep -m1 '^log_level:' config.yaml 2>/dev/null | awk '{print $2}' | tr -d ' ')
LANGGRAPH_LOG_LEVEL="${LANGGRAPH_LOG_LEVEL:-${CONFIG_LOG_LEVEL:-info}}"
LANGGRAPH_JOBS_PER_WORKER="${LANGGRAPH_JOBS_PER_WORKER:-10}"
@@ -150,85 +265,54 @@ if [ "${SKIP_LANGGRAPH_SERVER:-0}" != "1" ]; then
if [ "$LANGGRAPH_ALLOW_BLOCKING" = "1" ]; then
LANGGRAPH_ALLOW_BLOCKING_FLAG="--allow-blocking"
fi
- (cd backend && NO_COLOR=1 uv run langgraph dev --no-browser $LANGGRAPH_ALLOW_BLOCKING_FLAG --n-jobs-per-worker "$LANGGRAPH_JOBS_PER_WORKER" --server-log-level $LANGGRAPH_LOG_LEVEL $LANGGRAPH_EXTRA_FLAGS > ../logs/langgraph.log 2>&1) &
- ./scripts/wait-for-port.sh 2024 60 "LangGraph" || {
- echo " See logs/langgraph.log for details"
- tail -20 logs/langgraph.log
- if grep -qE "config_version|outdated|Environment variable .* not found|KeyError|ValidationError|config\.yaml" logs/langgraph.log 2>/dev/null; then
- echo ""
- echo " Hint: This may be a configuration issue. Try running 'make config-upgrade' to update your config.yaml."
- fi
- cleanup
- }
- echo "✓ LangGraph server started on localhost:2024"
+ run_service "LangGraph" \
+ "cd backend && NO_COLOR=1 uv run langgraph dev --no-browser $LANGGRAPH_ALLOW_BLOCKING_FLAG --n-jobs-per-worker $LANGGRAPH_JOBS_PER_WORKER --server-log-level $LANGGRAPH_LOG_LEVEL $LANGGRAPH_EXTRA_FLAGS > ../logs/langgraph.log 2>&1" \
+ 2024 60
else
- echo "⏩ Skipping LangGraph server (SKIP_LANGGRAPH_SERVER=1)"
- echo " Use /api/langgraph-compat/* via Gateway instead"
+ echo "⏩ Skipping LangGraph (Gateway mode — runtime embedded in Gateway)"
fi
-echo "Starting Gateway API..."
-(cd backend && PYTHONPATH=. uv run uvicorn app.gateway.app:app --host 0.0.0.0 --port 8001 $GATEWAY_EXTRA_FLAGS > ../logs/gateway.log 2>&1) &
-./scripts/wait-for-port.sh 8001 30 "Gateway API" || {
- echo "✗ Gateway API failed to start. Last log output:"
- tail -60 logs/gateway.log
- echo ""
- echo "Likely configuration errors:"
- grep -E "Failed to load configuration|Environment variable .* not found|config\.yaml.*not found" logs/gateway.log | tail -5 || true
- echo ""
- echo " Hint: Try running 'make config-upgrade' to update your config.yaml with the latest fields."
- cleanup
-}
-echo "✓ Gateway API started on localhost:8001"
+# 2. Gateway API
+run_service "Gateway" \
+ "cd backend && PYTHONPATH=. uv run uvicorn app.gateway.app:app --host 0.0.0.0 --port 8001 $GATEWAY_EXTRA_FLAGS > ../logs/gateway.log 2>&1" \
+ 8001 30
-echo "Starting Frontend..."
-(cd frontend && $FRONTEND_CMD > ../logs/frontend.log 2>&1) &
-./scripts/wait-for-port.sh 3000 120 "Frontend" || {
- echo " See logs/frontend.log for details"
- tail -20 logs/frontend.log
- cleanup
-}
-echo "✓ Frontend started on localhost:3000"
+# 3. Frontend
+run_service "Frontend" \
+ "cd frontend && $FRONTEND_CMD > ../logs/frontend.log 2>&1" \
+ 3000 120
-echo "Starting Nginx reverse proxy..."
-nginx -g 'daemon off;' -c "$REPO_ROOT/docker/nginx/nginx.local.conf" -p "$REPO_ROOT" > logs/nginx.log 2>&1 &
-NGINX_PID=$!
-./scripts/wait-for-port.sh 2026 10 "Nginx" || {
- echo " See logs/nginx.log for details"
- tail -10 logs/nginx.log
- cleanup
-}
-echo "✓ Nginx started on localhost:2026"
+# 4. Nginx
+run_service "Nginx" \
+ "nginx -g 'daemon off;' -c '$REPO_ROOT/docker/nginx/nginx.local.conf' -p '$REPO_ROOT' > logs/nginx.log 2>&1" \
+ 2026 10
-# ── Ready ─────────────────────────────────────────────────────────────────────
+# ── Ready ────────────────────────────────────────────────────────────────────
echo ""
echo "=========================================="
-if $DEV_MODE; then
- echo " ✓ DeerFlow development server is running!"
-else
- echo " ✓ DeerFlow production server is running!"
-fi
+echo " ✓ DeerFlow is running! [$MODE_LABEL]"
echo "=========================================="
echo ""
-echo " 🌐 Application: http://localhost:2026"
-echo " 📡 API Gateway: http://localhost:2026/api/*"
-if [ "${SKIP_LANGGRAPH_SERVER:-0}" = "1" ]; then
- echo " 🤖 LangGraph: skipped (SKIP_LANGGRAPH_SERVER=1)"
+echo " 🌐 http://localhost:2026"
+echo ""
+if $GATEWAY_MODE; then
+ echo " Routing: Frontend → Nginx → Gateway (embedded runtime)"
+ echo " API: /api/langgraph-compat/* → Gateway agent runtime"
else
- echo " 🤖 LangGraph: http://localhost:2026/api/langgraph/* (served by langgraph dev)"
-fi
-echo " 🧪 LangGraph Compat (experimental): http://localhost:2026/api/langgraph-compat/* (served by Gateway)"
-if [ "${SKIP_LANGGRAPH_SERVER:-0}" = "1" ]; then
- echo ""
- echo " 💡 Set NEXT_PUBLIC_LANGGRAPH_BASE_URL=/api/langgraph-compat in frontend/.env.local"
+ echo " Routing: Frontend → Nginx → LangGraph + Gateway"
+ echo " API: /api/langgraph/* → LangGraph server (2024)"
fi
+echo " /api/* → Gateway REST API (8001)"
echo ""
-echo " 📋 Logs:"
-echo " - LangGraph: logs/langgraph.log"
-echo " - Gateway: logs/gateway.log"
-echo " - Frontend: logs/frontend.log"
-echo " - Nginx: logs/nginx.log"
+echo " 📋 Logs: logs/{langgraph,gateway,frontend,nginx}.log"
echo ""
-echo "Press Ctrl+C to stop all services"
-wait
+if $DAEMON_MODE; then
+ echo " 🛑 Stop: make stop"
+ # Detach — trap is no longer needed
+ trap - INT TERM
+else
+ echo " Press Ctrl+C to stop all services"
+ wait
+fi
diff --git a/scripts/start-daemon.sh b/scripts/start-daemon.sh
index cce8c65bb..8822b73a0 100755
--- a/scripts/start-daemon.sh
+++ b/scripts/start-daemon.sh
@@ -1,146 +1,9 @@
#!/usr/bin/env bash
#
-# start-daemon.sh - Start all DeerFlow development services in daemon mode
+# start-daemon.sh — Start DeerFlow in daemon (background) mode
#
-# This script starts DeerFlow services in the background without keeping
-# the terminal connection. Logs are written to separate files.
-#
-# Must be run from the repo root directory.
-
-set -e
+# Thin wrapper around serve.sh --daemon.
+# Kept for backward compatibility.
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
-cd "$REPO_ROOT"
-
-# ── Stop existing services ────────────────────────────────────────────────────
-
-echo "Stopping existing services if any..."
-pkill -f "langgraph dev" 2>/dev/null || true
-pkill -f "uvicorn app.gateway.app:app" 2>/dev/null || true
-pkill -f "next dev" 2>/dev/null || true
-nginx -c "$REPO_ROOT/docker/nginx/nginx.local.conf" -p "$REPO_ROOT" -s quit 2>/dev/null || true
-sleep 1
-pkill -9 nginx 2>/dev/null || true
-./scripts/cleanup-containers.sh deer-flow-sandbox 2>/dev/null || true
-sleep 1
-
-# ── Banner ────────────────────────────────────────────────────────────────────
-
-echo ""
-echo "=========================================="
-echo " Starting DeerFlow in Daemon Mode"
-echo "=========================================="
-echo ""
-
-# ── Config check ─────────────────────────────────────────────────────────────
-
-if ! { \
- [ -n "$DEER_FLOW_CONFIG_PATH" ] && [ -f "$DEER_FLOW_CONFIG_PATH" ] || \
- [ -f backend/config.yaml ] || \
- [ -f config.yaml ]; \
- }; then
- echo "✗ No DeerFlow config file found."
- echo " Checked these locations:"
- echo " - $DEER_FLOW_CONFIG_PATH (when DEER_FLOW_CONFIG_PATH is set)"
- echo " - backend/config.yaml"
- echo " - ./config.yaml"
- echo ""
- echo " Run 'make config' from the repo root to generate ./config.yaml, then set required model API keys in .env or your config file."
- exit 1
-fi
-
-# ── Auto-upgrade config ──────────────────────────────────────────────────
-
-"$REPO_ROOT/scripts/config-upgrade.sh"
-
-# ── Cleanup on failure ───────────────────────────────────────────────────────
-
-cleanup_on_failure() {
- echo "Failed to start services, cleaning up..."
- pkill -f "langgraph dev" 2>/dev/null || true
- pkill -f "uvicorn app.gateway.app:app" 2>/dev/null || true
- pkill -f "next dev" 2>/dev/null || true
- nginx -c "$REPO_ROOT/docker/nginx/nginx.local.conf" -p "$REPO_ROOT" -s quit 2>/dev/null || true
- sleep 1
- pkill -9 nginx 2>/dev/null || true
- echo "✓ Cleanup complete"
-}
-
-trap cleanup_on_failure INT TERM
-
-# ── Start services ────────────────────────────────────────────────────────────
-
-mkdir -p logs
-mkdir -p temp/client_body_temp temp/proxy_temp temp/fastcgi_temp temp/uwsgi_temp temp/scgi_temp
-
-echo "Starting LangGraph server..."
-LANGGRAPH_JOBS_PER_WORKER="${LANGGRAPH_JOBS_PER_WORKER:-10}"
-LANGGRAPH_ALLOW_BLOCKING="${LANGGRAPH_ALLOW_BLOCKING:-0}"
-LANGGRAPH_ALLOW_BLOCKING_FLAG=""
-if [ "$LANGGRAPH_ALLOW_BLOCKING" = "1" ]; then
- LANGGRAPH_ALLOW_BLOCKING_FLAG="--allow-blocking"
-fi
-nohup sh -c "cd backend && NO_COLOR=1 uv run langgraph dev --no-browser ${LANGGRAPH_ALLOW_BLOCKING_FLAG} --no-reload --n-jobs-per-worker ${LANGGRAPH_JOBS_PER_WORKER} > ../logs/langgraph.log 2>&1" &
-./scripts/wait-for-port.sh 2024 60 "LangGraph" || {
- echo "✗ LangGraph failed to start. Last log output:"
- tail -60 logs/langgraph.log
- if grep -qE "config_version|outdated|Environment variable .* not found|KeyError|ValidationError|config\.yaml" logs/langgraph.log 2>/dev/null; then
- echo ""
- echo " Hint: This may be a configuration issue. Try running 'make config-upgrade' to update your config.yaml."
- fi
- cleanup_on_failure
- exit 1
-}
-echo "✓ LangGraph server started on localhost:2024"
-
-echo "Starting Gateway API..."
-nohup sh -c 'cd backend && PYTHONPATH=. uv run uvicorn app.gateway.app:app --host 0.0.0.0 --port 8001 > ../logs/gateway.log 2>&1' &
-./scripts/wait-for-port.sh 8001 30 "Gateway API" || {
- echo "✗ Gateway API failed to start. Last log output:"
- tail -60 logs/gateway.log
- echo ""
- echo " Hint: Try running 'make config-upgrade' to update your config.yaml with the latest fields."
- cleanup_on_failure
- exit 1
-}
-echo "✓ Gateway API started on localhost:8001"
-
-echo "Starting Frontend..."
-nohup sh -c 'cd frontend && pnpm run dev > ../logs/frontend.log 2>&1' &
-./scripts/wait-for-port.sh 3000 120 "Frontend" || {
- echo "✗ Frontend failed to start. Last log output:"
- tail -60 logs/frontend.log
- cleanup_on_failure
- exit 1
-}
-echo "✓ Frontend started on localhost:3000"
-
-echo "Starting Nginx reverse proxy..."
-nohup sh -c 'nginx -g "daemon off;" -c "$1/docker/nginx/nginx.local.conf" -p "$1" > logs/nginx.log 2>&1' _ "$REPO_ROOT" &
-./scripts/wait-for-port.sh 2026 10 "Nginx" || {
- echo "✗ Nginx failed to start. Last log output:"
- tail -60 logs/nginx.log
- cleanup_on_failure
- exit 1
-}
-echo "✓ Nginx started on localhost:2026"
-
-# ── Ready ─────────────────────────────────────────────────────────────────────
-
-echo ""
-echo "=========================================="
-echo " DeerFlow is running in daemon mode!"
-echo "=========================================="
-echo ""
-echo " 🌐 Application: http://localhost:2026"
-echo " 📡 API Gateway: http://localhost:2026/api/*"
-echo " 🤖 LangGraph: http://localhost:2026/api/langgraph/*"
-echo ""
-echo " 📋 Logs:"
-echo " - LangGraph: logs/langgraph.log"
-echo " - Gateway: logs/gateway.log"
-echo " - Frontend: logs/frontend.log"
-echo " - Nginx: logs/nginx.log"
-echo ""
-echo " 🛑 Stop daemon: make stop"
-echo ""
+exec "$REPO_ROOT/scripts/serve.sh" --dev --daemon "$@"