Skip to content

Commit 107b314

Browse files
committed
Merge branch 'main' into rayhpeng/persistence-scaffold
# Conflicts: # backend/Dockerfile # backend/uv.lock
2 parents b94383c + ca2fb95 commit 107b314

37 files changed

Lines changed: 2274 additions & 455 deletions

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ Every pull request runs the backend regression workflow at [.github/workflows/ba
310310

311311
- [Configuration Guide](backend/docs/CONFIGURATION.md) - Setup and configuration
312312
- [Architecture Overview](backend/CLAUDE.md) - Technical architecture
313-
- [MCP Setup Guide](MCP_SETUP.md) - Model Context Protocol configuration
313+
- [MCP Setup Guide](backend/docs/MCP_SERVER.md) - Model Context Protocol configuration
314314

315315
## Need Help?
316316

Makefile

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# DeerFlow - Unified Development Environment
22

3-
.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
3+
.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
44

55
BASH ?= bash
66

@@ -20,18 +20,25 @@ help:
2020
@echo " make install - Install all dependencies (frontend + backend)"
2121
@echo " make setup-sandbox - Pre-pull sandbox container image (recommended)"
2222
@echo " make dev - Start all services in development mode (with hot-reloading)"
23-
@echo " make dev-daemon - Start all services in background (daemon mode)"
23+
@echo " make dev-pro - Start in dev + Gateway mode (experimental, no LangGraph server)"
24+
@echo " make dev-daemon - Start dev services in background (daemon mode)"
25+
@echo " make dev-daemon-pro - Start dev daemon + Gateway mode (experimental)"
2426
@echo " make start - Start all services in production mode (optimized, no hot-reloading)"
27+
@echo " make start-pro - Start in prod + Gateway mode (experimental)"
28+
@echo " make start-daemon - Start prod services in background (daemon mode)"
29+
@echo " make start-daemon-pro - Start prod daemon + Gateway mode (experimental)"
2530
@echo " make stop - Stop all running services"
2631
@echo " make clean - Clean up processes and temporary files"
2732
@echo ""
2833
@echo "Docker Production Commands:"
2934
@echo " make up - Build and start production Docker services (localhost:2026)"
35+
@echo " make up-pro - Build and start production Docker in Gateway mode (experimental)"
3036
@echo " make down - Stop and remove production Docker containers"
3137
@echo ""
3238
@echo "Docker Development Commands:"
3339
@echo " make docker-init - Pull the sandbox image"
3440
@echo " make docker-start - Start Docker services (mode-aware from config.yaml, localhost:2026)"
41+
@echo " make docker-start-pro - Start Docker in Gateway mode (experimental, no LangGraph container)"
3542
@echo " make docker-stop - Stop Docker development services"
3643
@echo " make docker-logs - View Docker development logs"
3744
@echo " make docker-logs-frontend - View Docker frontend logs"
@@ -105,6 +112,15 @@ else
105112
@./scripts/serve.sh --dev
106113
endif
107114

115+
# Start all services in dev + Gateway mode (experimental: agent runtime embedded in Gateway)
116+
dev-pro:
117+
@$(PYTHON) ./scripts/check.py
118+
ifeq ($(OS),Windows_NT)
119+
@call scripts\run-with-git-bash.cmd ./scripts/serve.sh --dev --gateway
120+
else
121+
@./scripts/serve.sh --dev --gateway
122+
endif
123+
108124
# Start all services in production mode (with optimizations)
109125
start:
110126
@$(PYTHON) ./scripts/check.py
@@ -114,30 +130,54 @@ else
114130
@./scripts/serve.sh --prod
115131
endif
116132

133+
# Start all services in prod + Gateway mode (experimental)
134+
start-pro:
135+
@$(PYTHON) ./scripts/check.py
136+
ifeq ($(OS),Windows_NT)
137+
@call scripts\run-with-git-bash.cmd ./scripts/serve.sh --prod --gateway
138+
else
139+
@./scripts/serve.sh --prod --gateway
140+
endif
141+
117142
# Start all services in daemon mode (background)
118143
dev-daemon:
119144
@$(PYTHON) ./scripts/check.py
120145
ifeq ($(OS),Windows_NT)
121-
@call scripts\run-with-git-bash.cmd ./scripts/start-daemon.sh
146+
@call scripts\run-with-git-bash.cmd ./scripts/serve.sh --dev --daemon
122147
else
123-
@./scripts/start-daemon.sh
148+
@./scripts/serve.sh --dev --daemon
149+
endif
150+
151+
# Start daemon + Gateway mode (experimental)
152+
dev-daemon-pro:
153+
@$(PYTHON) ./scripts/check.py
154+
ifeq ($(OS),Windows_NT)
155+
@call scripts\run-with-git-bash.cmd ./scripts/serve.sh --dev --gateway --daemon
156+
else
157+
@./scripts/serve.sh --dev --gateway --daemon
158+
endif
159+
160+
# Start prod services in daemon mode (background)
161+
start-daemon:
162+
@$(PYTHON) ./scripts/check.py
163+
ifeq ($(OS),Windows_NT)
164+
@call scripts\run-with-git-bash.cmd ./scripts/serve.sh --prod --daemon
165+
else
166+
@./scripts/serve.sh --prod --daemon
167+
endif
168+
169+
# Start prod daemon + Gateway mode (experimental)
170+
start-daemon-pro:
171+
@$(PYTHON) ./scripts/check.py
172+
ifeq ($(OS),Windows_NT)
173+
@call scripts\run-with-git-bash.cmd ./scripts/serve.sh --prod --gateway --daemon
174+
else
175+
@./scripts/serve.sh --prod --gateway --daemon
124176
endif
125177

126178
# Stop all services
127179
stop:
128-
@echo "Stopping all services..."
129-
@-pkill -f "langgraph dev" 2>/dev/null || true
130-
@-pkill -f "uvicorn app.gateway.app:app" 2>/dev/null || true
131-
@-pkill -f "next dev" 2>/dev/null || true
132-
@-pkill -f "next start" 2>/dev/null || true
133-
@-pkill -f "next-server" 2>/dev/null || true
134-
@-pkill -f "next-server" 2>/dev/null || true
135-
@-nginx -c $(PWD)/docker/nginx/nginx.local.conf -p $(PWD) -s quit 2>/dev/null || true
136-
@sleep 1
137-
@-pkill -9 nginx 2>/dev/null || true
138-
@echo "Cleaning up sandbox containers..."
139-
@-./scripts/cleanup-containers.sh deer-flow-sandbox 2>/dev/null || true
140-
@echo "✓ All services stopped"
180+
@./scripts/serve.sh --stop
141181

142182
# Clean up
143183
clean: stop
@@ -159,6 +199,10 @@ docker-init:
159199
docker-start:
160200
@./scripts/docker.sh start
161201

202+
# Start Docker in Gateway mode (experimental)
203+
docker-start-pro:
204+
@./scripts/docker.sh start --gateway
205+
162206
# Stop Docker development environment
163207
docker-stop:
164208
@./scripts/docker.sh stop
@@ -181,6 +225,10 @@ docker-logs-gateway:
181225
up:
182226
@./scripts/deploy.sh
183227

228+
# Build and start production services in Gateway mode
229+
up-pro:
230+
@./scripts/deploy.sh --gateway
231+
184232
# Stop and remove production containers
185233
down:
186234
@./scripts/deploy.sh down

README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,60 @@ On Windows, run the local development flow from Git Bash. Native `cmd.exe` and P
280280

281281
6. **Access**: http://localhost:2026
282282

283+
#### Startup Modes
284+
285+
DeerFlow supports multiple startup modes across two dimensions:
286+
287+
- **Dev / Prod** — dev enables hot-reload; prod uses pre-built frontend
288+
- **Standard / Gateway** — standard uses a separate LangGraph server (4 processes); Gateway mode (experimental) embeds the agent runtime in the Gateway API (3 processes)
289+
290+
| | **Local Foreground** | **Local Daemon** | **Docker Dev** | **Docker Prod** |
291+
|---|---|---|---|---|
292+
| **Dev** | `./scripts/serve.sh --dev`<br/>`make dev` | `./scripts/serve.sh --dev --daemon`<br/>`make dev-daemon` | `./scripts/docker.sh start`<br/>`make docker-start` | — |
293+
| **Dev + Gateway** | `./scripts/serve.sh --dev --gateway`<br/>`make dev-pro` | `./scripts/serve.sh --dev --gateway --daemon`<br/>`make dev-daemon-pro` | `./scripts/docker.sh start --gateway`<br/>`make docker-start-pro` | — |
294+
| **Prod** | `./scripts/serve.sh --prod`<br/>`make start` | `./scripts/serve.sh --prod --daemon`<br/>`make start-daemon` | — | `./scripts/deploy.sh`<br/>`make up` |
295+
| **Prod + Gateway** | `./scripts/serve.sh --prod --gateway`<br/>`make start-pro` | `./scripts/serve.sh --prod --gateway --daemon`<br/>`make start-daemon-pro` | — | `./scripts/deploy.sh --gateway`<br/>`make up-pro` |
296+
297+
| Action | Local | Docker Dev | Docker Prod |
298+
|---|---|---|---|
299+
| **Stop** | `./scripts/serve.sh --stop`<br/>`make stop` | `./scripts/docker.sh stop`<br/>`make docker-stop` | `./scripts/deploy.sh down`<br/>`make down` |
300+
| **Restart** | `./scripts/serve.sh --restart [flags]` | `./scripts/docker.sh restart` | — |
301+
302+
> **Gateway mode** eliminates the LangGraph server process — the Gateway API handles agent execution directly via async tasks, managing its own concurrency.
303+
304+
#### Why Gateway Mode?
305+
306+
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:
307+
308+
| | Standard Mode | Gateway Mode |
309+
|---|---|---|
310+
| **Architecture** | Gateway (REST API) + LangGraph (agent runtime) | Gateway embeds agent runtime |
311+
| **Concurrency** | `--n-jobs-per-worker` per worker (requires license) | `--workers` × async tasks (no per-worker cap) |
312+
| **Containers / Processes** | 4 (frontend, gateway, langgraph, nginx) | 3 (frontend, gateway, nginx) |
313+
| **Resource usage** | Higher (two Python runtimes) | Lower (single Python runtime) |
314+
| **LangGraph Platform license** | Required for production images | Not required |
315+
| **Cold start** | Slower (two services to initialize) | Faster |
316+
317+
Both modes are functionally equivalent — the same agents, tools, and skills work in either mode.
318+
319+
#### Docker Production Deployment
320+
321+
`deploy.sh` supports building and starting separately. Images are mode-agnostic — runtime mode is selected at start time:
322+
323+
```bash
324+
# One-step (build + start)
325+
deploy.sh # standard mode (default)
326+
deploy.sh --gateway # gateway mode
327+
328+
# Two-step (build once, start with any mode)
329+
deploy.sh build # build all images
330+
deploy.sh start # start in standard mode
331+
deploy.sh start --gateway # start in gateway mode
332+
333+
# Stop
334+
deploy.sh down
335+
```
336+
283337
### Advanced
284338
#### Sandbox Mode
285339

backend/CLAUDE.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ DeerFlow is a LangGraph-based AI super agent system with a full-stack architectu
1313
- **Nginx** (port 2026): Unified reverse proxy entry point
1414
- **Provisioner** (port 8002, optional in Docker dev): Started only when sandbox is configured for provisioner/Kubernetes mode
1515

16+
**Runtime Modes**:
17+
- **Standard mode** (`make dev`): LangGraph Server handles agent execution as a separate process. 4 processes total.
18+
- **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.
19+
1620
**Project Structure**:
1721
```
1822
deer-flow/
@@ -80,6 +84,8 @@ When making code changes, you MUST update the relevant documentation:
8084
make check # Check system requirements
8185
make install # Install all dependencies (frontend + backend)
8286
make dev # Start all services (LangGraph + Gateway + Frontend + Nginx), with config.yaml preflight
87+
make dev-pro # Gateway mode (experimental): skip LangGraph, agent runtime embedded in Gateway
88+
make start-pro # Production + Gateway mode (experimental)
8389
make stop # Stop all services
8490
```
8591

@@ -436,8 +442,25 @@ make dev
436442

437443
This starts all services and makes the application available at `http://localhost:2026`.
438444

445+
**All startup modes:**
446+
447+
| | **Local Foreground** | **Local Daemon** | **Docker Dev** | **Docker Prod** |
448+
|---|---|---|---|---|
449+
| **Dev** | `./scripts/serve.sh --dev`<br/>`make dev` | `./scripts/serve.sh --dev --daemon`<br/>`make dev-daemon` | `./scripts/docker.sh start`<br/>`make docker-start` ||
450+
| **Dev + Gateway** | `./scripts/serve.sh --dev --gateway`<br/>`make dev-pro` | `./scripts/serve.sh --dev --gateway --daemon`<br/>`make dev-daemon-pro` | `./scripts/docker.sh start --gateway`<br/>`make docker-start-pro` ||
451+
| **Prod** | `./scripts/serve.sh --prod`<br/>`make start` | `./scripts/serve.sh --prod --daemon`<br/>`make start-daemon` || `./scripts/deploy.sh`<br/>`make up` |
452+
| **Prod + Gateway** | `./scripts/serve.sh --prod --gateway`<br/>`make start-pro` | `./scripts/serve.sh --prod --gateway --daemon`<br/>`make start-daemon-pro` || `./scripts/deploy.sh --gateway`<br/>`make up-pro` |
453+
454+
| Action | Local | Docker Dev | Docker Prod |
455+
|---|---|---|---|
456+
| **Stop** | `./scripts/serve.sh --stop`<br/>`make stop` | `./scripts/docker.sh stop`<br/>`make docker-stop` | `./scripts/deploy.sh down`<br/>`make down` |
457+
| **Restart** | `./scripts/serve.sh --restart [flags]` | `./scripts/docker.sh restart` ||
458+
459+
Gateway mode embeds the agent runtime in Gateway, no LangGraph server.
460+
439461
**Nginx routing**:
440-
- `/api/langgraph/*` → LangGraph Server (2024)
462+
- Standard mode: `/api/langgraph/*` → LangGraph Server (2024)
463+
- Gateway mode: `/api/langgraph/*` → Gateway embedded runtime (8001) (via envsubst)
441464
- `/api/*` (other) → Gateway API (8001)
442465
- `/` (non-API) → Frontend (3000)
443466

backend/Dockerfile

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
# Backend Development Dockerfile
1+
# Backend Dockerfile — multi-stage build
2+
# Stage 1 (builder): compiles native Python extensions with build-essential
3+
# Stage 2 (dev): retains toolchain for dev containers (uv sync at startup)
4+
# Stage 3 (runtime): clean image without compiler toolchain for production
25

36
# UV source image (override for restricted networks that cannot reach ghcr.io)
47
ARG UV_IMAGE=ghcr.io/astral-sh/uv:0.7.20
58
FROM ${UV_IMAGE} AS uv-source
69

7-
FROM python:3.12-slim-bookworm
10+
# ── Stage 1: Builder ──────────────────────────────────────────────────────────
11+
FROM python:3.12-slim-bookworm AS builder
812

913
ARG NODE_MAJOR=22
1014
ARG APT_MIRROR
@@ -19,7 +23,7 @@ RUN if [ -n "${APT_MIRROR}" ]; then \
1923
sed -i "s|deb.debian.org|${APT_MIRROR}|g" /etc/apt/sources.list 2>/dev/null || true; \
2024
fi
2125

22-
# Install system dependencies + Node.js (provides npx for MCP servers)
26+
# Install build tools + Node.js (build-essential needed for native Python extensions)
2327
RUN apt-get update && apt-get install -y \
2428
curl \
2529
build-essential \
@@ -32,23 +36,54 @@ RUN apt-get update && apt-get install -y \
3236
&& apt-get install -y nodejs \
3337
&& rm -rf /var/lib/apt/lists/*
3438

35-
# Install Docker CLI (for DooD: allows starting sandbox containers via host Docker socket)
36-
COPY --from=docker:cli /usr/local/bin/docker /usr/local/bin/docker
37-
3839
# Install uv (source image overridable via UV_IMAGE build arg)
3940
COPY --from=uv-source /uv /uvx /usr/local/bin/
4041

4142
# Set working directory
4243
WORKDIR /app
4344

44-
# Copy frontend source code
45+
# Copy backend source code
4546
COPY backend ./backend
4647

4748
# Install dependencies with cache mount
4849
# When UV_EXTRAS is set (e.g. "postgres"), installs optional dependencies.
4950
RUN --mount=type=cache,target=/root/.cache/uv \
5051
sh -c "cd backend && UV_INDEX_URL=${UV_INDEX_URL:-https://pypi.org/simple} uv sync ${UV_EXTRAS:+--extra $UV_EXTRAS}"
5152

53+
# ── Stage 2: Dev ──────────────────────────────────────────────────────────────
54+
# Retains compiler toolchain from builder so startup-time `uv sync` can build
55+
# source distributions in development containers.
56+
FROM builder AS dev
57+
58+
# Install Docker CLI (for DooD: allows starting sandbox containers via host Docker socket)
59+
COPY --from=docker:cli /usr/local/bin/docker /usr/local/bin/docker
60+
61+
EXPOSE 8001 2024
62+
63+
CMD ["sh", "-c", "cd backend && PYTHONPATH=. uv run uvicorn app.gateway.app:app --host 0.0.0.0 --port 8001"]
64+
65+
# ── Stage 3: Runtime ──────────────────────────────────────────────────────────
66+
# Clean image without build-essential — reduces size (~200 MB) and attack surface.
67+
FROM python:3.12-slim-bookworm
68+
69+
# Copy Node.js runtime from builder (provides npx for MCP servers)
70+
COPY --from=builder /usr/bin/node /usr/bin/node
71+
COPY --from=builder /usr/lib/node_modules /usr/lib/node_modules
72+
RUN ln -s ../lib/node_modules/npm/bin/npm-cli.js /usr/bin/npm \
73+
&& ln -s ../lib/node_modules/npm/bin/npx-cli.js /usr/bin/npx
74+
75+
# Install Docker CLI (for DooD: allows starting sandbox containers via host Docker socket)
76+
COPY --from=docker:cli /usr/local/bin/docker /usr/local/bin/docker
77+
78+
# Install uv (source image overridable via UV_IMAGE build arg)
79+
COPY --from=uv-source /uv /uvx /usr/local/bin/
80+
81+
# Set working directory
82+
WORKDIR /app
83+
84+
# Copy backend with pre-built virtualenv from builder
85+
COPY --from=builder /app/backend ./backend
86+
5287
# Expose ports (gateway: 8001, langgraph: 2024)
5388
EXPOSE 8001 2024
5489

backend/app/channels/slack.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def __init__(self, bus: MessageBus, config: dict[str, Any]) -> None:
3030
self._socket_client = None
3131
self._web_client = None
3232
self._loop: asyncio.AbstractEventLoop | None = None
33-
self._allowed_users: set[str] = set(config.get("allowed_users", []))
33+
self._allowed_users: set[str] = {str(user_id) for user_id in config.get("allowed_users", [])}
3434

3535
async def start(self) -> None:
3636
if self._running:

backend/app/gateway/routers/agents.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class AgentResponse(BaseModel):
2424
description: str = Field(default="", description="Agent description")
2525
model: str | None = Field(default=None, description="Optional model override")
2626
tool_groups: list[str] | None = Field(default=None, description="Optional tool group whitelist")
27-
soul: str | None = Field(default=None, description="SOUL.md content (included on GET /{name})")
27+
soul: str | None = Field(default=None, description="SOUL.md content")
2828

2929

3030
class AgentsListResponse(BaseModel):
@@ -92,17 +92,17 @@ def _agent_config_to_response(agent_cfg: AgentConfig, include_soul: bool = False
9292
"/agents",
9393
response_model=AgentsListResponse,
9494
summary="List Custom Agents",
95-
description="List all custom agents available in the agents directory.",
95+
description="List all custom agents available in the agents directory, including their soul content.",
9696
)
9797
async def list_agents() -> AgentsListResponse:
9898
"""List all custom agents.
9999
100100
Returns:
101-
List of all custom agents with their metadata (without soul content).
101+
List of all custom agents with their metadata and soul content.
102102
"""
103103
try:
104104
agents = list_custom_agents()
105-
return AgentsListResponse(agents=[_agent_config_to_response(a) for a in agents])
105+
return AgentsListResponse(agents=[_agent_config_to_response(a, include_soul=True) for a in agents])
106106
except Exception as e:
107107
logger.error(f"Failed to list agents: {e}", exc_info=True)
108108
raise HTTPException(status_code=500, detail=f"Failed to list agents: {str(e)}")

backend/docs/AUTO_TITLE_GENERATION.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ def after_agent(self, state: TitleMiddlewareState, runtime: Runtime) -> dict | N
248248
- [`packages/harness/deerflow/agents/thread_state.py`](../packages/harness/deerflow/agents/thread_state.py) - ThreadState 定义
249249
- [`packages/harness/deerflow/agents/middlewares/title_middleware.py`](../packages/harness/deerflow/agents/middlewares/title_middleware.py) - TitleMiddleware 实现
250250
- [`packages/harness/deerflow/config/title_config.py`](../packages/harness/deerflow/config/title_config.py) - 配置管理
251-
- [`config.yaml`](../config.yaml) - 配置文件
251+
- [`config.yaml`](../../config.example.yaml) - 配置文件
252252
- [`packages/harness/deerflow/agents/lead_agent/agent.py`](../packages/harness/deerflow/agents/lead_agent/agent.py) - Middleware 注册
253253

254254
## 参考资料

0 commit comments

Comments
 (0)