diff --git a/frontend/src/components/workspace/input-box.tsx b/frontend/src/components/workspace/input-box.tsx
index ddc682744a..99ec72c390 100644
--- a/frontend/src/components/workspace/input-box.tsx
+++ b/frontend/src/components/workspace/input-box.tsx
@@ -55,6 +55,7 @@ import {
DropdownMenuLabel,
DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu";
+import { fetch } from "@/core/api/fetcher";
import { getBackendBaseURL } from "@/core/config";
import { useI18n } from "@/core/i18n/hooks";
import { useModels } from "@/core/models/hooks";
@@ -154,6 +155,7 @@ export function InputBox({
const [followupsLoading, setFollowupsLoading] = useState(false);
const lastGeneratedForAiIdRef = useRef(null);
const wasStreamingRef = useRef(false);
+ const messagesRef = useRef(thread.messages);
const [confirmOpen, setConfirmOpen] = useState(false);
const [pendingSuggestion, setPendingSuggestion] = useState(
@@ -353,6 +355,10 @@ export function InputBox({
followupsVisibilityChangeRef.current?.(showFollowups);
}, [showFollowups]);
+ useEffect(() => {
+ messagesRef.current = thread.messages;
+ }, [thread.messages]);
+
useEffect(() => {
return () => followupsVisibilityChangeRef.current?.(false);
}, []);
@@ -369,14 +375,16 @@ export function InputBox({
return;
}
- const lastAi = [...thread.messages].reverse().find((m) => m.type === "ai");
+ const lastAi = [...messagesRef.current]
+ .reverse()
+ .find((m) => m.type === "ai");
const lastAiId = lastAi?.id ?? null;
if (!lastAiId || lastAiId === lastGeneratedForAiIdRef.current) {
return;
}
lastGeneratedForAiIdRef.current = lastAiId;
- const recent = thread.messages
+ const recent = messagesRef.current
.filter((m) => m.type === "human" || m.type === "ai")
.map((m) => {
const role = m.type === "human" ? "user" : "assistant";
@@ -426,7 +434,7 @@ export function InputBox({
});
return () => controller.abort();
- }, [context.model_name, disabled, isMock, status, thread.messages, threadId]);
+ }, [context.model_name, disabled, isMock, status, threadId]);
return (
diff --git a/frontend/src/components/workspace/messages/message-list-item.tsx b/frontend/src/components/workspace/messages/message-list-item.tsx
index ca96eac4a5..d5d13891a7 100644
--- a/frontend/src/components/workspace/messages/message-list-item.tsx
+++ b/frontend/src/components/workspace/messages/message-list-item.tsx
@@ -1,8 +1,15 @@
import type { Message } from "@langchain/langgraph-sdk";
-import { FileIcon, Loader2Icon } from "lucide-react";
+import {
+ FileIcon,
+ Loader2Icon,
+ ThumbsDownIcon,
+ ThumbsUpIcon,
+} from "lucide-react";
import {
memo,
+ useCallback,
useMemo,
+ useState,
type AnchorHTMLAttributes,
type ImgHTMLAttributes,
} from "react";
@@ -22,6 +29,11 @@ import {
} from "@/components/ai-elements/reasoning";
import { Task, TaskTrigger } from "@/components/ai-elements/task";
import { Badge } from "@/components/ui/badge";
+import {
+ deleteFeedback,
+ upsertFeedback,
+ type FeedbackData,
+} from "@/core/api/feedback";
import { resolveArtifactURL } from "@/core/artifacts/utils";
import { useI18n } from "@/core/i18n/hooks";
import {
@@ -40,18 +52,89 @@ import { CopyButton } from "../copy-button";
import { MarkdownContent } from "./markdown-content";
import { MessageTokenUsage } from "./message-token-usage";
+function FeedbackButtons({
+ threadId,
+ runId,
+ initialFeedback,
+}: {
+ threadId: string;
+ runId: string;
+ initialFeedback: FeedbackData | null;
+}) {
+ const [feedback, setFeedback] = useState
(
+ initialFeedback,
+ );
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ const handleClick = useCallback(
+ async (rating: number) => {
+ if (isSubmitting) return;
+ setIsSubmitting(true);
+ try {
+ if (feedback?.rating === rating) {
+ await deleteFeedback(threadId, runId);
+ setFeedback(null);
+ } else {
+ const result = await upsertFeedback(threadId, runId, rating);
+ setFeedback(result);
+ }
+ } catch {
+ // Revert on error — feedback state unchanged on catch
+ } finally {
+ setIsSubmitting(false);
+ }
+ },
+ [threadId, runId, feedback, isSubmitting],
+ );
+
+ return (
+
+
+
+
+ );
+}
+
export function MessageListItem({
className,
+ threadId,
message,
isLoading,
- threadId,
tokenUsageEnabled = false,
+ feedback,
+ runId,
}: {
className?: string;
message: Message;
isLoading?: boolean;
threadId: string;
tokenUsageEnabled?: boolean;
+ feedback?: FeedbackData | null;
+ runId?: string;
}) {
const isHuman = message.type === "human";
return (
@@ -70,7 +153,7 @@ export function MessageListItem({
@@ -81,6 +164,13 @@ export function MessageListItem({
""
}
/>
+ {feedback !== undefined && runId && threadId && (
+
+ )}
)}
diff --git a/frontend/src/components/workspace/messages/message-list.tsx b/frontend/src/components/workspace/messages/message-list.tsx
index d1d02c6d0e..1ba6015699 100644
--- a/frontend/src/components/workspace/messages/message-list.tsx
+++ b/frontend/src/components/workspace/messages/message-list.tsx
@@ -1,9 +1,12 @@
import type { BaseStream } from "@langchain/langgraph-sdk/react";
+import { ChevronUpIcon, Loader2Icon } from "lucide-react";
+import { useCallback, useEffect, useRef } from "react";
import {
Conversation,
ConversationContent,
} from "@/components/ai-elements/conversation";
+import { Button } from "@/components/ui/button";
import { useI18n } from "@/core/i18n/hooks";
import {
extractContentFromMessage,
@@ -34,23 +37,137 @@ import { SubtaskCard } from "./subtask-card";
export const MESSAGE_LIST_DEFAULT_PADDING_BOTTOM = 160;
export const MESSAGE_LIST_FOLLOWUPS_EXTRA_PADDING_BOTTOM = 80;
+const LOAD_MORE_HISTORY_THROTTLE_MS = 1200;
+
+function LoadMoreHistoryIndicator({
+ isLoading,
+ hasMore,
+ loadMore,
+}: {
+ isLoading?: boolean;
+ hasMore?: boolean;
+ loadMore?: () => void;
+}) {
+ const { t } = useI18n();
+ const sentinelRef = useRef(null);
+ const timeoutRef = useRef | null>(null);
+ const lastLoadRef = useRef(0);
+
+ const throttledLoadMore = useCallback(() => {
+ if (!hasMore || isLoading) {
+ return;
+ }
+
+ const now = Date.now();
+ const remaining =
+ LOAD_MORE_HISTORY_THROTTLE_MS - (now - lastLoadRef.current);
+
+ if (remaining <= 0) {
+ lastLoadRef.current = now;
+ loadMore?.();
+ return;
+ }
+
+ if (timeoutRef.current) {
+ return;
+ }
+
+ timeoutRef.current = setTimeout(() => {
+ timeoutRef.current = null;
+ if (!hasMore || isLoading) {
+ return;
+ }
+ lastLoadRef.current = Date.now();
+ loadMore?.();
+ }, remaining);
+ }, [hasMore, isLoading, loadMore]);
+
+ useEffect(() => {
+ const element = sentinelRef.current;
+ if (!element || !hasMore) {
+ return;
+ }
+
+ const observer = new IntersectionObserver(
+ ([entry]) => {
+ if (entry?.isIntersecting) {
+ throttledLoadMore();
+ }
+ },
+ {
+ rootMargin: "120px 0px 0px 0px",
+ },
+ );
+
+ observer.observe(element);
+
+ return () => {
+ observer.disconnect();
+ };
+ }, [hasMore, throttledLoadMore]);
+
+ useEffect(() => {
+ return () => {
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current);
+ }
+ };
+ }, []);
+
+ if (!hasMore && !isLoading) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+}
+
export function MessageList({
className,
threadId,
thread,
paddingBottom = MESSAGE_LIST_DEFAULT_PADDING_BOTTOM,
tokenUsageEnabled = false,
+ hasMoreHistory,
+ loadMoreHistory,
+ isHistoryLoading,
}: {
className?: string;
threadId: string;
thread: BaseStream;
paddingBottom?: number;
tokenUsageEnabled?: boolean;
+ hasMoreHistory?: boolean;
+ loadMoreHistory?: () => void;
+ isHistoryLoading?: boolean;
}) {
const { t } = useI18n();
const rehypePlugins = useRehypeSplitWordsIntoSpans(thread.isLoading);
const updateSubtask = useUpdateSubtask();
const messages = thread.messages;
+
if (thread.isThreadLoading && messages.length === 0) {
return ;
}
@@ -58,16 +175,21 @@ export function MessageList({
-
+
+
{groupMessages(messages, (group) => {
if (group.type === "human" || group.type === "assistant") {
return group.messages.map((msg) => {
return (
);
@@ -183,7 +305,7 @@ export function MessageList({
results.push(
{t.subtasks.executing(tasks.size)}
,
diff --git a/frontend/src/components/workspace/settings/account-settings-page.tsx b/frontend/src/components/workspace/settings/account-settings-page.tsx
new file mode 100644
index 0000000000..6d3df844d5
--- /dev/null
+++ b/frontend/src/components/workspace/settings/account-settings-page.tsx
@@ -0,0 +1,133 @@
+"use client";
+
+import { LogOutIcon } from "lucide-react";
+import { useState } from "react";
+
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { fetch, getCsrfHeaders } from "@/core/api/fetcher";
+import { useAuth } from "@/core/auth/AuthProvider";
+import { parseAuthError } from "@/core/auth/types";
+
+import { SettingsSection } from "./settings-section";
+
+export function AccountSettingsPage() {
+ const { user, logout } = useAuth();
+ const [currentPassword, setCurrentPassword] = useState("");
+ const [newPassword, setNewPassword] = useState("");
+ const [confirmPassword, setConfirmPassword] = useState("");
+ const [message, setMessage] = useState("");
+ const [error, setError] = useState("");
+ const [loading, setLoading] = useState(false);
+
+ const handleChangePassword = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setError("");
+ setMessage("");
+
+ if (newPassword !== confirmPassword) {
+ setError("New passwords do not match");
+ return;
+ }
+ if (newPassword.length < 8) {
+ setError("Password must be at least 8 characters");
+ return;
+ }
+
+ setLoading(true);
+ try {
+ const res = await fetch("/api/v1/auth/change-password", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ ...getCsrfHeaders(),
+ },
+ body: JSON.stringify({
+ current_password: currentPassword,
+ new_password: newPassword,
+ }),
+ });
+
+ if (!res.ok) {
+ const data = await res.json();
+ const authError = parseAuthError(data);
+ setError(authError.message);
+ return;
+ }
+
+ setMessage("Password changed successfully");
+ setCurrentPassword("");
+ setNewPassword("");
+ setConfirmPassword("");
+ } catch {
+ setError("Network error. Please try again.");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
+
+
+ Email
+ {user?.email ?? "—"}
+ Role
+
+ {user?.system_role ?? "—"}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/workspace/settings/settings-dialog.tsx b/frontend/src/components/workspace/settings/settings-dialog.tsx
index fadc25fa69..6e9fa5ddf9 100644
--- a/frontend/src/components/workspace/settings/settings-dialog.tsx
+++ b/frontend/src/components/workspace/settings/settings-dialog.tsx
@@ -6,6 +6,7 @@ import {
BrainIcon,
PaletteIcon,
SparklesIcon,
+ UserIcon,
WrenchIcon,
} from "lucide-react";
import { useEffect, useMemo, useState } from "react";
@@ -18,6 +19,7 @@ import {
} from "@/components/ui/dialog";
import { ScrollArea } from "@/components/ui/scroll-area";
import { AboutSettingsPage } from "@/components/workspace/settings/about-settings-page";
+import { AccountSettingsPage } from "@/components/workspace/settings/account-settings-page";
import { AppearanceSettingsPage } from "@/components/workspace/settings/appearance-settings-page";
import { MemorySettingsPage } from "@/components/workspace/settings/memory-settings-page";
import { NotificationSettingsPage } from "@/components/workspace/settings/notification-settings-page";
@@ -27,6 +29,7 @@ import { useI18n } from "@/core/i18n/hooks";
import { cn } from "@/lib/utils";
type SettingsSection =
+ | "account"
| "appearance"
| "memory"
| "tools"
@@ -54,6 +57,11 @@ export function SettingsDialog(props: SettingsDialogProps) {
const sections = useMemo(
() => [
+ {
+ id: "account",
+ label: t.settings.sections.account,
+ icon: UserIcon,
+ },
{
id: "appearance",
label: t.settings.sections.appearance,
@@ -74,6 +82,7 @@ export function SettingsDialog(props: SettingsDialogProps) {
{ id: "about", label: t.settings.sections.about, icon: InfoIcon },
],
[
+ t.settings.sections.account,
t.settings.sections.appearance,
t.settings.sections.memory,
t.settings.sections.tools,
@@ -122,8 +131,9 @@ export function SettingsDialog(props: SettingsDialogProps) {
})}
-
-
+
+
+ {activeSection === "account" &&
}
{activeSection === "appearance" &&
}
{activeSection === "memory" &&
}
{activeSection === "tools" &&
}
diff --git a/frontend/src/content/en/_meta.ts b/frontend/src/content/en/_meta.ts
index 49b33a7848..2451ce3be6 100644
--- a/frontend/src/content/en/_meta.ts
+++ b/frontend/src/content/en/_meta.ts
@@ -25,6 +25,15 @@ const meta: MetaRecord = {
blog: {
type: "page",
},
+ posts: {
+ type: "page",
+ },
+ login: {
+ type: "page",
+ },
+ setup: {
+ type: "page",
+ },
};
export default meta;
diff --git a/frontend/src/content/en/application/agents-and-threads.mdx b/frontend/src/content/en/application/agents-and-threads.mdx
index 9ad5eb851a..bbf3cfc7e3 100644
--- a/frontend/src/content/en/application/agents-and-threads.mdx
+++ b/frontend/src/content/en/application/agents-and-threads.mdx
@@ -1,3 +1,141 @@
+---
+title: Agents and Threads
+description: DeerFlow App supports multiple named agents and maintains conversation state across sessions through threads and checkpointing.
+---
+
+import { Callout, Cards, Steps } from "nextra/components";
+
# Agents and Threads
-TBD
+DeerFlow App supports multiple named agents and maintains conversation state across sessions through threads and checkpointing.
+
+## Agents
+
+### The default agent
+
+The default agent is the Lead Agent with no custom configuration. It loads all globally enabled skills, has access to all configured tools, and uses the first model in `config.yaml` as its default.
+
+### Custom agents
+
+Custom agents are named variants of the Lead Agent. Each one can have:
+
+- a **display name** and an auto-derived ASCII slug (the `name` used internally)
+- a specific **model** to use by default
+- a restricted set of **skills** (or all globally enabled skills if unspecified)
+- a restricted set of **tool groups**
+- a custom **system prompt** or agent-specific instructions
+
+Custom agents are created and managed through:
+
+- **The App UI**: open the Agents section in the settings panel.
+- **The Gateway API**: `POST /api/agents` with the agent definition.
+
+The slug (`name`) is automatically derived from the `display_name` and must be unique. The system checks for conflicts and appends a suffix if needed (`/api/agents/check`).
+
+Agent configuration is stored in `agents/{name}/config.yaml` relative to the backend directory.
+
+### Restricting agent capabilities
+
+To restrict a custom agent to specific skills:
+
+```yaml
+# agents/my-researcher/config.yaml
+name: my-researcher
+display_name: My Researcher
+skills:
+ - deep-research
+ - academic-paper-review
+# Omit skills key entirely to inherit all globally enabled skills
+# Set skills: [] to disable all skills for this agent
+```
+
+To restrict to specific tool groups:
+
+```yaml
+tool_groups:
+ - research # only tools in the 'research' group are available
+```
+
+## Threads
+
+A **thread** is a persistent conversation session. Each thread has:
+
+- a unique thread ID
+- a message history
+- accumulated artifacts (output files)
+- a title (auto-generated after the first exchange)
+- a reference to the agent used
+
+Threads are listed in the sidebar. Clicking a thread resumes the conversation from where it left off.
+
+### Thread lifecycle
+
+
+
+#### Create
+
+A new thread is created when you start a conversation. The thread ID is generated and the initial configuration (model, agent, skills) is recorded.
+
+#### Run
+
+Each user message triggers an agent run. The run streams tokens and tool calls back to the browser in real time. The thread state (messages, artifacts) is updated after each turn.
+
+#### Checkpoint
+
+If a checkpointer is configured, the thread state is persisted after each turn. This means the conversation survives process restarts.
+
+#### Resume
+
+Opening a thread from the sidebar loads its full history from the checkpointer. The agent picks up from where it left off.
+
+
+
+### Checkpointer configuration
+
+The checkpointer controls how thread state is persisted:
+
+```yaml
+# In-memory (default if omitted — state lost on restart)
+# checkpointer:
+# type: memory
+
+# SQLite (survives restarts, single-process)
+checkpointer:
+ type: sqlite
+ connection_string: checkpoints.db
+
+# PostgreSQL (multi-process, production)
+# checkpointer:
+# type: postgres
+# connection_string: postgresql://user:password@localhost:5432/deerflow
+```
+
+
+ The LangGraph Server manages its own state separately. The
+ checkpointer setting in config.yaml applies to the
+ embedded DeerFlowClient (used in direct Python integrations), not
+ to the LangGraph Server deployment used by DeerFlow App.
+
+
+### Thread data storage
+
+Working files produced during a thread (uploads, intermediate files, output artifacts) are stored under:
+
+```
+backend/.deer-flow/threads/{thread_id}/user-data/
+```
+
+This directory is mounted into the sandbox as `/mnt/user-data/` for the agent to read and write.
+
+In Docker deployments, this path is bind-mounted from the host so that thread data persists across container restarts. Set `DEER_FLOW_ROOT` to the absolute path of your deer-flow repository root to ensure the correct host path is used.
+
+
+
+
+
diff --git a/frontend/src/content/en/application/configuration.mdx b/frontend/src/content/en/application/configuration.mdx
index 79de38b76b..3eeca52a14 100644
--- a/frontend/src/content/en/application/configuration.mdx
+++ b/frontend/src/content/en/application/configuration.mdx
@@ -1,3 +1,280 @@
+---
+title: Configuration
+description: DeerFlow App is configured through two files and a set of environment variables. This page covers the application-level configuration that most operators need to set up before deploying.
+---
+
+import { Callout, Cards, Tabs } from "nextra/components";
+
# Configuration
-TBD
+DeerFlow App is configured through two files and a set of environment variables. This page covers the application-level configuration that most operators need to set up before deploying.
+
+## Configuration files
+
+| File | Purpose |
+| ------------------------ | --------------------------------------------------------------------------------------- |
+| `config.yaml` | Backend configuration: models, sandbox, tools, skills, memory, and all Harness settings |
+| `extensions_config.json` | MCP servers and skill enable/disable state (managed by the App UI and Gateway API) |
+
+Frontend environment variables control the Next.js build and runtime behavior.
+
+## config.yaml
+
+Start by copying the example:
+
+```bash
+cp config.example.yaml config.yaml
+```
+
+The most important sections for application configuration are:
+
+### Models
+
+Configure the LLM providers the agent can use. At least one model is required.
+
+
+
+```yaml
+models:
+ - name: gpt-4o
+ use: langchain_openai:ChatOpenAI
+ model: gpt-4o
+ api_key: $OPENAI_API_KEY
+ request_timeout: 600.0
+ max_retries: 2
+ supports_vision: true
+```
+
+
+```yaml
+models:
+ - name: claude-3-5-sonnet
+ use: langchain_anthropic:ChatAnthropic
+ model: claude-3-5-sonnet-20241022
+ api_key: $ANTHROPIC_API_KEY
+ default_request_timeout: 600.0
+ max_retries: 2
+ max_tokens: 8192
+ supports_vision: true
+ supports_thinking: true
+ when_thinking_enabled:
+ thinking:
+ type: enabled
+ when_thinking_disabled:
+ thinking:
+ type: disabled
+```
+
+
+```yaml
+models:
+ - name: deepseek-v3
+ use: deerflow.models.patched_deepseek:PatchedChatDeepSeek
+ model: deepseek-reasoner
+ api_key: $DEEPSEEK_API_KEY
+ timeout: 600.0
+ max_retries: 2
+ supports_thinking: true
+ when_thinking_enabled:
+ extra_body:
+ thinking:
+ type: enabled
+ when_thinking_disabled:
+ extra_body:
+ thinking:
+ type: disabled
+```
+
+
+```yaml
+models:
+ - name: qwen3-local
+ use: langchain_ollama:ChatOllama
+ model: qwen3:32b
+ base_url: http://localhost:11434 # No /v1 suffix — uses native Ollama API
+ num_predict: 8192
+ temperature: 0.7
+ reasoning: true
+ supports_thinking: true
+ supports_vision: false
+```
+
+Install Ollama provider: `cd backend && uv add 'deerflow-harness[ollama]'`
+
+
+ Use langchain_ollama:ChatOllama (not the OpenAI-compatible
+ endpoint) for Ollama models. The native API correctly separates thinking
+ content; the OpenAI-compatible endpoint may flatten or drop it.
+
+
+
+```yaml
+models:
+ - name: gemini-2.5-pro
+ use: langchain_google_genai:ChatGoogleGenerativeAI
+ model: gemini-2.5-pro
+ gemini_api_key: $GEMINI_API_KEY
+ timeout: 600.0
+ max_retries: 2
+ max_tokens: 8192
+ supports_vision: true
+```
+
+
+
+### Sandbox
+
+Choose the execution environment for agent file and command operations:
+
+
+
+```yaml
+sandbox:
+ use: deerflow.sandbox.local:LocalSandboxProvider
+ allow_host_bash: false # set true only for trusted single-user workflows
+```
+
+
+```yaml
+sandbox:
+ use: deerflow.community.aio_sandbox:AioSandboxProvider
+ image: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest
+ replicas: 3
+ idle_timeout: 600
+```
+
+Install: `cd backend && uv add 'deerflow-harness[aio-sandbox]'`
+
+
+
+```yaml
+sandbox:
+ use: deerflow.community.aio_sandbox:AioSandboxProvider
+ provisioner_url: http://provisioner:8002
+```
+
+
+
+### Tools
+
+Configure which tools the agent has access to. The defaults use DuckDuckGo (no API key) and Jina AI for web operations:
+
+```yaml
+tools:
+ # Web search (choose one)
+ - use: deerflow.community.ddg_search.tools:web_search_tool # default, no key required
+ # - use: deerflow.community.tavily.tools:web_search_tool
+ # api_key: $TAVILY_API_KEY
+
+ # Web fetch (choose one)
+ - use: deerflow.community.jina_ai.tools:web_fetch_tool
+
+ # Image search
+ - use: deerflow.community.image_search.tools:image_search_tool
+
+ # File operations
+ - use: deerflow.sandbox.tools:ls_tool
+ - use: deerflow.sandbox.tools:read_file_tool
+ - use: deerflow.sandbox.tools:glob_tool
+ - use: deerflow.sandbox.tools:grep_tool
+ - use: deerflow.sandbox.tools:write_file_tool
+ - use: deerflow.sandbox.tools:str_replace_tool
+ - use: deerflow.sandbox.tools:bash_tool
+```
+
+### Thread state persistence (checkpointer)
+
+By default, DeerFlow uses an SQLite checkpointer for thread state persistence:
+
+```yaml
+checkpointer:
+ type: sqlite
+ connection_string: checkpoints.db # stored in backend/.deer-flow/
+```
+
+For production deployments with multiple processes:
+
+```yaml
+checkpointer:
+ type: postgres
+ connection_string: postgresql://user:password@localhost:5432/deerflow
+```
+
+Install PostgreSQL support: `cd backend && uv add langgraph-checkpoint-postgres psycopg[binary] psycopg-pool`
+
+For in-memory only (state lost on restart):
+
+```yaml
+checkpointer:
+ type: memory
+```
+
+### Memory
+
+```yaml
+memory:
+ enabled: true
+ storage_path: memory.json
+ debounce_seconds: 30
+ max_facts: 100
+ injection_enabled: true
+ max_injection_tokens: 2000
+```
+
+## Frontend environment variables
+
+Set these before running `pnpm build` or starting the frontend in production:
+
+| Variable | Required | Description |
+| --------------------- | -------------------------- | ---------------------------------------------------------------- |
+| `BETTER_AUTH_SECRET` | **Required** in production | Secret for session signing. Use `openssl rand -base64 32`. |
+| `BETTER_AUTH_URL` | Recommended | Public-facing base URL (e.g., `https://your-domain.com`) |
+| `SKIP_ENV_VALIDATION` | Optional | Set to `1` to skip env validation during build (not recommended) |
+| `NEXT_PUBLIC_API_URL` | Optional | Override the API base URL for the frontend |
+
+In development, set these in a `.env` file at the repo root:
+
+```bash
+BETTER_AUTH_SECRET=your-strong-secret-here-min-32-chars
+```
+
+## extensions_config.json
+
+This file manages MCP server connections and skill enable/disable state. It is created automatically when you first manage extensions through the App UI or Gateway API.
+
+Manual example:
+
+```json
+{
+ "mcpServers": {
+ "my-server": {
+ "command": "npx",
+ "args": ["-y", "@my-org/my-mcp-server"],
+ "enabled": true
+ }
+ },
+ "skills": {
+ "deep-research": { "enabled": true },
+ "data-analysis": { "enabled": true }
+ }
+}
+```
+
+## Config upgrade
+
+When the config schema changes, `config_version` is bumped. To merge new fields into your existing config without losing customizations:
+
+```bash
+make config-upgrade
+```
+
+
+
+
+
diff --git a/frontend/src/content/en/application/deployment-guide.mdx b/frontend/src/content/en/application/deployment-guide.mdx
index 05fe722896..04b3599c00 100644
--- a/frontend/src/content/en/application/deployment-guide.mdx
+++ b/frontend/src/content/en/application/deployment-guide.mdx
@@ -1,3 +1,217 @@
+---
+title: Deployment Guide
+description: "This guide covers all supported deployment methods for DeerFlow App: local development, Docker Compose, and production with Kubernetes-managed sandboxes."
+---
+
+import { Callout, Cards, Steps, Tabs } from "nextra/components";
+
# Deployment Guide
-TBD
+This guide covers all supported deployment methods for DeerFlow App: local development, Docker Compose, and production with Kubernetes-managed sandboxes.
+
+## Local development deployment
+
+The local workflow is the fastest way to run DeerFlow. All services run as native processes on your machine.
+
+
+
+```bash
+make dev
+```
+
+Services started:
+
+| Service | Port | Description |
+| ----------- | ---- | ------------------------ |
+| LangGraph | 2024 | DeerFlow Harness runtime |
+| Gateway API | 8001 | FastAPI backend |
+| Frontend | 3000 | Next.js UI |
+| nginx | 2026 | Unified reverse proxy |
+
+Access the app at **http://localhost:2026**.
+
+
+
+```bash
+make stop
+```
+
+Stops all four services. Safe to run even if a service is not running.
+
+
+
+```
+logs/langgraph.log # Agent runtime logs
+logs/gateway.log # API gateway logs
+logs/frontend.log # Next.js dev server logs
+logs/nginx.log # nginx access/error logs
+```
+
+Tail a log in real time:
+
+```bash
+tail -f logs/langgraph.log
+```
+
+
+
+
+## Docker Compose deployment
+
+Docker Compose runs all services in containers. Use this for a more production-like local setup or for team environments.
+
+### Prerequisites
+
+- Docker (or Docker Desktop / OrbStack on macOS)
+- A configured `config.yaml` at the repo root
+
+### Development compose
+
+```bash
+# Set the absolute path to your deer-flow repo root
+export DEER_FLOW_ROOT=/path/to/deer-flow
+
+docker compose -f docker/docker-compose-dev.yaml up --build
+```
+
+Services: nginx, frontend, gateway, langgraph, and optionally provisioner (for K8s-managed sandboxes).
+
+Access the app at **http://localhost:2026**.
+
+### Environment variables
+
+Create a `.env` file in the repo root for secrets and runtime configuration:
+
+```bash
+# .env
+OPENAI_API_KEY=sk-...
+DEER_FLOW_ROOT=/absolute/path/to/deer-flow
+BETTER_AUTH_SECRET=your-secret-here-min-32-chars
+```
+
+The `docker-compose*.yaml` files include an `env_file: ../.env` directive that loads this automatically.
+
+
+ Always set BETTER_AUTH_SECRET to a strong random string before
+ deploying. Without it, the frontend build uses a default that is publicly
+ known.
+
+
+### Data persistence
+
+Thread data is stored in `backend/.deer-flow/threads/`. In Docker deployments, this directory is bind-mounted into the langgraph container.
+
+To avoid data loss when containers are recreated:
+
+1. Set `DEER_FLOW_ROOT` to the absolute repo root path (or a stable host path).
+2. Verify the `threads/` and `skills/` directories are mounted correctly.
+
+For production, use a named volume or a Persistent Volume Claim (PVC) instead of a host bind-mount.
+
+## Production deployment considerations
+
+### Sandbox mode selection
+
+| Sandbox | Use case |
+| -------------------------------------- | ------------------------------------------ |
+| `LocalSandboxProvider` | Single-user, trusted local workflows |
+| `AioSandboxProvider` (Docker) | Multi-user, moderate isolation requirement |
+| `AioSandboxProvider` + K8s Provisioner | Production, strong isolation, multi-user |
+
+For any deployment with more than one concurrent user, use a container-based sandbox to prevent users from interfering with each other's execution environments.
+
+### K8s Provisioner setup
+
+The provisioner manages sandbox Pods in a Kubernetes cluster. It is included in `docker/docker-compose-dev.yaml`.
+
+
+
+#### Configure the provisioner
+
+Set required environment variables in your `.env` or compose override:
+
+```bash
+K8S_NAMESPACE=deer-flow
+SANDBOX_IMAGE=enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest
+DEER_FLOW_ROOT=/absolute/path/to/deer-flow
+```
+
+#### Configure the sandbox provider
+
+```yaml
+# config.yaml
+sandbox:
+ use: deerflow.community.aio_sandbox:AioSandboxProvider
+ provisioner_url: http://provisioner:8002
+```
+
+#### Configure data persistence
+
+For production, use PVCs instead of hostPath volumes:
+
+```bash
+# In .env or compose environment
+USERDATA_PVC_NAME=deer-flow-userdata-pvc
+SKILLS_PVC_NAME=deer-flow-skills-pvc
+```
+
+When `USERDATA_PVC_NAME` is set, the provisioner automatically uses subPath (`threads/{thread_id}/user-data`) so each thread gets its own directory in the PVC.
+
+
+
+### nginx configuration
+
+nginx routes all traffic. Key environment variables that control routing:
+
+| Variable | Default | Description |
+| -------------------- | ---------------- | --------------------------------------- |
+| `LANGGRAPH_UPSTREAM` | `langgraph:2024` | LangGraph service address |
+| `LANGGRAPH_REWRITE` | `/` | URL rewrite prefix for LangGraph routes |
+
+These are set in the Docker Compose environment and processed by `envsubst` at container startup.
+
+### Authentication
+
+DeerFlow App uses [Better Auth](https://www.better-auth.com/) for session management. In production:
+
+1. Set `BETTER_AUTH_SECRET` to a strong random string (minimum 32 characters).
+2. Set `BETTER_AUTH_URL` to your public-facing URL (e.g., `https://your-domain.com`).
+
+```bash
+# Generate a secret
+openssl rand -base64 32
+```
+
+### Resource recommendations
+
+| Service | Minimum | Recommended |
+| ------------------------------- | ---------------- | ---------------- |
+| LangGraph (agent runtime) | 2 vCPU, 4 GB RAM | 4 vCPU, 8 GB RAM |
+| Gateway | 0.5 vCPU, 512 MB | 1 vCPU, 1 GB |
+| Frontend | 0.5 vCPU, 512 MB | 1 vCPU, 1 GB |
+| Sandbox container (per session) | 1 vCPU, 1 GB | 2 vCPU, 2 GB |
+
+## Deployment verification
+
+After starting, verify the deployment:
+
+```bash
+# Check Gateway health
+curl http://localhost:8001/health
+
+# Check LangGraph health
+curl http://localhost:2024/ok
+
+# List configured models (through nginx)
+curl http://localhost:2026/api/models
+```
+
+A working deployment returns a `200` response from each endpoint. The `/api/models` call returns the list of models from your `config.yaml`.
+
+
+
+
+
diff --git a/frontend/src/content/en/application/index.mdx b/frontend/src/content/en/application/index.mdx
index a951ea81b1..2cb15a911a 100644
--- a/frontend/src/content/en/application/index.mdx
+++ b/frontend/src/content/en/application/index.mdx
@@ -1,3 +1,75 @@
+---
+title: DeerFlow App
+description: DeerFlow App is the reference implementation of what a production DeerFlow experience looks like. It assembles the Harness runtime, a web-based conversation workspace, an API gateway, and a reverse proxy into a single deployable system.
+---
+
+import { Callout, Cards } from "nextra/components";
+
# DeerFlow App
-TBD
+
+ DeerFlow App is a complete Super Agent application built on top of DeerFlow
+ Harness. It packages the runtime capabilities into a ready-to-deploy product
+ with a web UI, API gateway, and operational tooling.
+
+
+DeerFlow App is the reference implementation of what a production DeerFlow experience looks like. It assembles the Harness runtime, a web-based conversation workspace, an API gateway, and a reverse proxy into a single deployable system.
+
+## What the App provides
+
+| Capability | Description |
+| ----------------------- | ---------------------------------------------------------------------------------------------------- |
+| **Web workspace** | Browser-based conversation UI with support for threads, artifacts, file uploads, and skill selection |
+| **Custom agents** | Create and manage named agents with different models, skills, and tool sets |
+| **Thread management** | Persistent conversation threads with checkpointing and history |
+| **Streaming responses** | Real-time token streaming with thinking steps and tool call visibility |
+| **Artifact viewer** | In-browser preview and download of files and outputs produced by the agent |
+| **Extensions UI** | Enable/disable MCP servers and skills without editing config files |
+| **Gateway API** | FastAPI-based REST API that bridges the frontend and the LangGraph runtime |
+
+## Architecture
+
+The DeerFlow App runs as four services behind a single nginx reverse proxy:
+
+```
+ ┌──────────────────┐
+ Browser → │ nginx :2026 │
+ └──────────────────┘
+ │ │
+ ┌────────┘ └────────┐
+ ▼ ▼
+┌──────────────────┐ ┌──────────────────────┐
+│ Frontend :3000 │ │ Gateway API :8001 │
+│ (Next.js) │ │ (FastAPI) │
+└──────────────────┘ └──────────────────────┘
+ │
+ ┌─────────┘
+ ▼
+ ┌──────────────────────┐
+ │ LangGraph :2024 │
+ │ (DeerFlow Harness) │
+ └──────────────────────┘
+```
+
+- **nginx**: routes requests — `/api/*` to the Gateway, LangGraph streaming endpoints to LangGraph directly, and everything else to the frontend.
+- **Frontend** (Next.js + React): the browser UI. Communicates with both the Gateway and LangGraph.
+- **Gateway** (FastAPI): handles API operations — model listing, agent CRUD, memory, extensions management, file uploads.
+- **LangGraph**: the DeerFlow Harness runtime. Manages thread state, agent execution, and streaming.
+
+## Technology stack
+
+| Layer | Technology |
+| ----------------- | -------------------------------------------------------------------- |
+| Frontend | Next.js 16, React 19, TypeScript, pnpm |
+| Gateway | FastAPI, Python 3.12, uvicorn |
+| Agent runtime | LangGraph, LangChain, DeerFlow Harness |
+| Reverse proxy | nginx |
+| State persistence | LangGraph Server (default) + optional SQLite/PostgreSQL checkpointer |
+
+
+
+
+
diff --git a/frontend/src/content/en/application/operations-and-troubleshooting.mdx b/frontend/src/content/en/application/operations-and-troubleshooting.mdx
index 6bf0225038..8b21cf4b41 100644
--- a/frontend/src/content/en/application/operations-and-troubleshooting.mdx
+++ b/frontend/src/content/en/application/operations-and-troubleshooting.mdx
@@ -1,3 +1,179 @@
+---
+title: Operations and Troubleshooting
+description: This page covers day-to-day operational tasks and solutions to common problems when running DeerFlow App.
+---
+
+import { Callout, Cards } from "nextra/components";
+
# Operations and Troubleshooting
-TBD
+This page covers day-to-day operational tasks and solutions to common problems when running DeerFlow App.
+
+## Log files
+
+All services write logs to the `logs/` directory when started with `make dev`:
+
+| File | Service |
+| -------------------- | ------------------------------------ |
+| `logs/langgraph.log` | LangGraph / DeerFlow Harness runtime |
+| `logs/gateway.log` | FastAPI Gateway API |
+| `logs/frontend.log` | Next.js frontend dev server |
+| `logs/nginx.log` | nginx reverse proxy |
+
+Tail logs in real time:
+
+```bash
+tail -f logs/langgraph.log
+tail -f logs/gateway.log
+```
+
+Adjust the runtime log level in `config.yaml`:
+
+```yaml
+log_level: debug # debug | info | warning | error
+```
+
+## Health checks
+
+Verify each service is responding:
+
+```bash
+# Gateway health
+curl http://localhost:8001/health
+
+# LangGraph health
+curl http://localhost:2024/ok
+
+# Through nginx (verifies full proxy chain)
+curl http://localhost:2026/api/models
+```
+
+## Config upgrade
+
+When you pull a new version of DeerFlow, the `config_version` in `config.example.yaml` may be higher than your `config.yaml`. To merge new fields without losing your customizations:
+
+```bash
+make config-upgrade
+```
+
+Check the current version in your config:
+
+```bash
+grep config_version config.yaml
+```
+
+## Common problems
+
+### The app loads but the agent doesn't respond
+
+1. Check `logs/langgraph.log` for startup errors.
+2. Verify your model is correctly configured in `config.yaml` with a valid API key.
+3. Confirm the API key environment variable is set in the shell that ran `make dev`.
+4. Test the model endpoint directly with `curl` to rule out network issues.
+
+---
+
+### Model API errors in the agent response
+
+The agent reports an error like `"No chat models are configured"` or `"model not found"`:
+
+- Check that the `models:` section in `config.yaml` has at least one entry.
+- Verify the `name:` field matches what you are requesting in the UI.
+- Check that `api_key:` is referencing the correct environment variable and that the variable is set.
+
+---
+
+### Frontend build fails with `BETTER_AUTH_SECRET`
+
+```
+Error: BETTER_AUTH_SECRET is required
+```
+
+Set the environment variable before building:
+
+```bash
+export BETTER_AUTH_SECRET=$(openssl rand -base64 32)
+pnpm build
+```
+
+Or set it in your `.env` file at the repo root.
+
+---
+
+### Sandbox-related tool failures
+
+If file tools (`read_file`, `ls`, `bash`) fail with permission or path errors:
+
+1. For `LocalSandboxProvider`: check that `allow_host_bash` is set correctly and that the agent has read/write access to the thread data directory.
+2. For container sandboxes: verify Docker (or Apple Container) is running and the sandbox image is accessible.
+3. For K8s Provisioner: check that the provisioner service is healthy (`curl http://localhost:8002/health`) and that the K8s cluster is reachable.
+
+---
+
+### K8s Provisioner not connecting
+
+```
+Connection refused: http://provisioner:8002
+```
+
+- Ensure the provisioner service is running: `docker compose ps`.
+- Check that `K8S_API_SERVER` is set correctly (should point to the K8s API from inside the container, not `localhost`).
+- Verify `~/.kube/config` is mounted and the cluster is reachable from the container host.
+
+---
+
+### MCP tools not loading
+
+If MCP tools appear in `extensions_config.json` but are not available in the agent:
+
+1. Check `logs/langgraph.log` for MCP initialization errors.
+2. Verify the MCP server command is installed (`npx`, `uvx`, or the relevant binary).
+3. Test the server command manually to confirm it starts without errors.
+4. Set `log_level: debug` to see detailed MCP loading output.
+
+---
+
+### Memory not persisting across sessions
+
+- Verify `memory.enabled: true` in `config.yaml`.
+- Check that the storage path is writable: `ls -la backend/.deer-flow/`.
+- Look for memory update errors in `logs/langgraph.log` (search for "memory").
+
+## Data backup
+
+Thread data and memory are stored under `backend/.deer-flow/`:
+
+```
+backend/.deer-flow/
+ memory.json # global agent memory
+ agents/ # per-agent memory
+ threads/ # thread working directories
+ {thread_id}/
+ user-data/
+ uploads/
+ outputs/
+ checkpoints.db # SQLite checkpoints (if configured)
+```
+
+Back up this entire directory to preserve conversation history, artifacts, and learned memory.
+
+In Docker deployments, the bind-mounted host path (`$DEER_FLOW_ROOT/backend/.deer-flow/`) is the source of truth — back up the host path.
+
+## Restarting services
+
+To restart a single service (local deployment):
+
+```bash
+make stop
+make dev
+```
+
+Individual service restart scripts are in `scripts/`. For targeted restarts, you can kill and relaunch individual processes manually using the PIDs in the log files.
+
+
+
+
+
diff --git a/frontend/src/content/en/application/quick-start.mdx b/frontend/src/content/en/application/quick-start.mdx
index 81c1028364..5ecfb3a26d 100644
--- a/frontend/src/content/en/application/quick-start.mdx
+++ b/frontend/src/content/en/application/quick-start.mdx
@@ -1,3 +1,133 @@
+---
+title: Quick Start
+description: This guide walks you through starting DeerFlow App on your local machine using the `make dev` workflow. All four services (LangGraph, Gateway, Frontend, nginx) start together and are accessible through a single URL.
+---
+
+import { Callout, Cards, Steps } from "nextra/components";
+
# Quick Start
-TBD
+
+ Get DeerFlow App running locally in about 10 minutes. You need a machine with
+ Python 3.12+, Node.js 22+, and at least one LLM API key.
+
+
+This guide walks you through starting DeerFlow App on your local machine using the `make dev` workflow. All four services (LangGraph, Gateway, Frontend, nginx) start together and are accessible through a single URL.
+
+## Prerequisites
+
+Check that all required tools are installed:
+
+```bash
+make check
+```
+
+Required:
+
+| Tool | Minimum version |
+| ------- | ------------------ |
+| Python | 3.12 |
+| uv | latest |
+| Node.js | 22 |
+| pnpm | 10 |
+| nginx | any recent version |
+
+On macOS, install with `brew install python uv node pnpm nginx`. On Linux, use your distribution's package manager.
+
+## Steps
+
+
+
+### Clone the repository
+
+```bash
+git clone https://github.com/bytedance/deer-flow.git
+cd deer-flow
+```
+
+### Install dependencies
+
+```bash
+make install
+```
+
+This installs both backend Python dependencies (via `uv`) and frontend Node.js dependencies (via `pnpm`).
+
+### Create your config file
+
+```bash
+cp config.example.yaml config.yaml
+```
+
+Then edit `config.yaml` to add at least one model. The minimum change is adding a model under the `models:` section:
+
+```yaml
+models:
+ - name: gpt-4o
+ use: langchain_openai:ChatOpenAI
+ model: gpt-4o
+ api_key: $OPENAI_API_KEY
+ request_timeout: 600.0
+ max_retries: 2
+ supports_vision: true
+```
+
+Set the corresponding environment variable before starting:
+
+```bash
+export OPENAI_API_KEY=sk-...
+```
+
+See the [Application Configuration](/docs/application/configuration) page for examples with other model providers.
+
+### Start all services
+
+```bash
+make dev
+```
+
+This starts:
+
+- LangGraph server on port `2024`
+- Gateway API on port `8001`
+- Frontend on port `3000`
+- nginx reverse proxy on port `2026`
+
+Open [http://localhost:2026](http://localhost:2026) in your browser.
+
+### Stop all services
+
+```bash
+make stop
+```
+
+
+
+## What happens when you run `make dev`
+
+- Existing service processes are stopped first (safe to run after an interrupted start).
+- Each service is started in the background and writes logs to the `logs/` directory.
+- nginx proxies all traffic through port `2026`, so you only need one URL.
+
+Log files:
+
+| Service | Log file |
+| --------- | -------------------- |
+| LangGraph | `logs/langgraph.log` |
+| Gateway | `logs/gateway.log` |
+| Frontend | `logs/frontend.log` |
+| nginx | `logs/nginx.log` |
+
+
+ If something is not working, check the log files first. Most startup errors
+ (missing API keys, config parsing failures) appear in `logs/langgraph.log` or
+ `logs/gateway.log`.
+
+
+
+
+
+
diff --git a/frontend/src/content/en/application/workspace-usage.mdx b/frontend/src/content/en/application/workspace-usage.mdx
index 567a6fb983..686614aa72 100644
--- a/frontend/src/content/en/application/workspace-usage.mdx
+++ b/frontend/src/content/en/application/workspace-usage.mdx
@@ -1,3 +1,82 @@
+---
+title: Workspace Usage
+description: The DeerFlow App workspace is a browser-based interface for having multi-turn conversations with the agent, tracking task progress, viewing artifacts, and managing files.
+---
+
+import { Callout, Cards } from "nextra/components";
+
# Workspace Usage
-TBD
+The DeerFlow App workspace is a browser-based interface for having multi-turn conversations with the agent, tracking task progress, viewing artifacts, and managing files.
+
+## Starting a conversation
+
+Open the app at `http://localhost:2026` (or your deployment URL). The workspace is split into:
+
+- **Sidebar** (left): thread list, new thread button, and navigation to agents and settings.
+- **Conversation area** (center): the active thread's message history.
+- **Input bar** (bottom): text input, skill selector, model selector, and attachment controls.
+
+To start a new thread, click **New Thread** in the sidebar or use the keyboard shortcut. Each thread is independent — it has its own conversation history, artifacts, and state.
+
+## Selecting a model
+
+Use the model picker in the input bar to choose which configured model to use for the current request. Models listed here correspond to the `models:` entries in your `config.yaml`.
+
+The selected model applies to the next message only. You can switch models between messages in the same thread.
+
+If a model supports **thinking mode**, a toggle appears next to the model selector. When thinking is enabled, the agent's internal reasoning steps are shown inline in the response.
+
+## Selecting a skill
+
+Click the **Skills** button in the input bar to open the skill selector. Enabled skills are listed here. Selecting a skill tells the agent to apply that skill's workflow and instructions for the current message.
+
+
+ Skills are most useful when you want the agent to follow a specific approach,
+ such as deep research methodology or structured data analysis. For general
+ questions, you typically do not need to select a skill.
+
+
+## Plan mode
+
+Toggle **Plan Mode** in the input bar to enable the todo list middleware. In plan mode, the agent creates and maintains a visible task list as it works through a complex multi-step objective. Each task shows its status (`pending`, `in_progress`, `completed`) in real time.
+
+Plan mode is most useful for complex tasks with 3 or more distinct steps.
+
+## Uploading files
+
+Click the attachment icon in the input bar to upload files. Supported file types include PDFs, text files, spreadsheets, and images.
+
+Uploaded files are stored under the thread's working directory (`/mnt/user-data/uploads/`) and are accessible to the agent during the conversation. PDFs are automatically converted to Markdown for better model comprehension (the converter can be configured via `uploads.pdf_converter` in `config.yaml`).
+
+## Viewing artifacts
+
+When the agent produces output files (reports, charts, code, etc.), they appear in the **Artifacts** panel. Each artifact shows a preview (for supported types) and a download link.
+
+Artifacts are tracked in the thread state and persist across page reloads.
+
+## Understanding the message stream
+
+Each agent response in the conversation may contain:
+
+- **Text**: the agent's direct reply.
+- **Thinking** (if thinking mode is enabled): the model's internal reasoning, shown in a collapsible block.
+- **Tool calls**: a record of which tools were called and with what arguments.
+- **Tool results**: the output returned by each tool.
+- **Subagent output**: if the agent delegated a task, the subagent's progress appears inline.
+
+Tool calls and thinking steps are collapsed by default. Click to expand them.
+
+## Switching agents
+
+If you have created custom agents, use the **Agent** selector in the input bar to switch to a different agent. The selected agent persists for the duration of the thread.
+
+Custom agents may have different models, skills, tool sets, and system prompts. See [Agents and Threads](/docs/application/agents-and-threads) for how to create and manage custom agents.
+
+
+
+
+
diff --git a/frontend/src/content/en/harness/_meta.ts b/frontend/src/content/en/harness/_meta.ts
index f96fd39f98..b682b71cb4 100644
--- a/frontend/src/content/en/harness/_meta.ts
+++ b/frontend/src/content/en/harness/_meta.ts
@@ -10,6 +10,12 @@ const meta: MetaRecord = {
"design-principles": {
title: "Design Principles",
},
+ "lead-agent": {
+ title: "Lead Agent",
+ },
+ middlewares: {
+ title: "Middlewares",
+ },
configuration: {
title: "Configuration",
},
@@ -25,6 +31,12 @@ const meta: MetaRecord = {
sandbox: {
title: "Sandbox",
},
+ subagents: {
+ title: "Subagents",
+ },
+ mcp: {
+ title: "MCP Integration",
+ },
customization: {
title: "Customization",
},
diff --git a/frontend/src/content/en/harness/configuration.mdx b/frontend/src/content/en/harness/configuration.mdx
index 79de38b76b..89de127160 100644
--- a/frontend/src/content/en/harness/configuration.mdx
+++ b/frontend/src/content/en/harness/configuration.mdx
@@ -1,3 +1,168 @@
+---
+title: Configuration
+description: "DeerFlow's configuration system is designed around one goal: every meaningful behavior should be expressible in a config file, not hardcoded in the application. This makes deployments reproducible, auditable, and easy to customize per environment."
+---
+
+import { Callout, Cards } from "nextra/components";
+
# Configuration
-TBD
+
+ All DeerFlow Harness behaviors are driven by config.yaml. One
+ file controls which models are available, how the sandbox runs, what tools are
+ loaded, and how each subsystem behaves.
+
+
+DeerFlow's configuration system is designed around one goal: every meaningful behavior should be expressible in a config file, not hardcoded in the application. This makes deployments reproducible, auditable, and easy to customize per environment.
+
+## Config file location
+
+DeerFlow resolves `config.yaml` using the following priority order:
+
+1. The path passed to `AppConfig.from_file(config_path)` explicitly.
+2. The `DEER_FLOW_CONFIG_PATH` environment variable.
+3. `backend/config.yaml` (relative to the backend directory).
+4. `config.yaml` in the repository root.
+
+If none of these paths exist, the application raises an error at startup.
+
+To use a custom location:
+
+```bash
+export DEER_FLOW_CONFIG_PATH=/path/to/my-config.yaml
+```
+
+## Environment variable interpolation
+
+Any field value can reference an environment variable using `$VAR_NAME` syntax:
+
+```yaml
+models:
+ - name: gpt-4o
+ api_key: $OPENAI_API_KEY
+```
+
+This keeps secrets out of the config file itself. The variable is resolved at runtime from the process environment.
+
+## The `use` field
+
+Many configuration entries use a `use:` field to specify the Python class or object to instantiate. The format is:
+
+```
+package.subpackage.module:ClassName
+```
+
+or for module-level objects:
+
+```
+package.subpackage.module:variable_name
+```
+
+Examples:
+
+```yaml
+sandbox:
+ use: deerflow.sandbox.local:LocalSandboxProvider
+
+tools:
+ - use: deerflow.community.tavily.tools:web_search_tool
+ api_key: $TAVILY_API_KEY
+```
+
+This pattern is how DeerFlow achieves pluggability without hardcoding class references.
+
+## Extra fields are passed through
+
+For model configuration, `ModelConfig` uses `pydantic ConfigDict(extra="allow")`. This means any extra fields you add under a model entry are passed directly to the model constructor. This allows provider-specific options (like `extra_body`, `reasoning`, or custom timeout keys) to work without modifying the harness:
+
+```yaml
+models:
+ - name: my-model
+ use: langchain_openai:ChatOpenAI
+ model: gpt-4o
+ api_key: $OPENAI_API_KEY
+ some_provider_specific_option: value # passed through to ChatOpenAI constructor
+```
+
+## Configuration version
+
+`config.yaml` includes a `config_version` field that tracks the schema version:
+
+```yaml
+config_version: 6
+```
+
+When the schema changes (new fields, renamed sections), this number is bumped. If your local `config.yaml` is behind the current version, run:
+
+```bash
+make config-upgrade
+```
+
+This merges new fields from `config.example.yaml` into your existing `config.yaml` without overwriting your customizations.
+
+## Module configuration reference
+
+The following table maps each top-level `config.yaml` section to its documentation page:
+
+| Section | Description | Documentation |
+| ----------------- | ------------------------------------------------ | -------------------------------------------------------- |
+| `log_level` | Logging level (`debug`/`info`/`warning`/`error`) | — |
+| `models` | Available LLM models | [Lead Agent](/docs/harness/lead-agent) |
+| `token_usage` | Token tracking per model call | [Middlewares](/docs/harness/middlewares) |
+| `tools` | Available agent tools | [Tools](/docs/harness/tools) |
+| `tool_groups` | Named groups of tools | [Tools](/docs/harness/tools) |
+| `tool_search` | Deferred/on-demand tool loading | [Tools](/docs/harness/tools) |
+| `sandbox` | Sandbox provider and options | [Sandbox](/docs/harness/sandbox) |
+| `skills` | Skills directory and container path | [Skills](/docs/harness/skills) |
+| `skill_evolution` | Agent-managed skill creation | [Skills](/docs/harness/skills) |
+| `subagents` | Subagent timeouts and max turns | [Subagents](/docs/harness/subagents) |
+| `acp_agents` | External ACP agent integrations | [Subagents](/docs/harness/subagents) |
+| `memory` | Cross-session memory storage | [Memory](/docs/harness/memory) |
+| `summarization` | Conversation summarization | [Middlewares](/docs/harness/middlewares) |
+| `title` | Automatic thread title generation | [Middlewares](/docs/harness/middlewares) |
+| `checkpointer` | Thread state persistence | [Agents & Threads](/docs/application/agents-and-threads) |
+| `guardrails` | Tool call authorization | — |
+| `stream_bridge` | Streaming configuration | — |
+| `uploads` | File upload settings (PDF converter) | — |
+| `channels` | IM channel integrations (Feishu, Slack, etc.) | — |
+
+## Minimal config to get started
+
+The minimum valid `config.yaml` requires at least one model and a sandbox:
+
+```yaml
+config_version: 6
+
+models:
+ - name: gpt-4o
+ use: langchain_openai:ChatOpenAI
+ model: gpt-4o
+ api_key: $OPENAI_API_KEY
+ request_timeout: 600.0
+ max_retries: 2
+ supports_vision: true
+
+sandbox:
+ use: deerflow.sandbox.local:LocalSandboxProvider
+
+tools:
+ - use: deerflow.community.ddg_search.tools:web_search_tool
+ - use: deerflow.community.jina_ai.tools:web_fetch_tool
+ - use: deerflow.sandbox.tools:ls_tool
+ - use: deerflow.sandbox.tools:read_file_tool
+ - use: deerflow.sandbox.tools:write_file_tool
+ - use: deerflow.sandbox.tools:bash_tool
+```
+
+Start from `config.example.yaml` in the repository root and uncomment the sections you need.
+
+
+
+
+
diff --git a/frontend/src/content/en/harness/customization.mdx b/frontend/src/content/en/harness/customization.mdx
index c900eb2ca7..2f538aeb6c 100644
--- a/frontend/src/content/en/harness/customization.mdx
+++ b/frontend/src/content/en/harness/customization.mdx
@@ -1,3 +1,178 @@
+---
+title: Customization
+description: DeerFlow's pluggable architecture means most parts of the system can be replaced or extended without forking the core. This page maps the extension points and explains how to use each one.
+---
+
+import { Callout, Cards } from "nextra/components";
+
# Customization
-TBD
+
+ DeerFlow is designed to be adapted. You can extend agent behavior by writing
+ custom middlewares, adding new tools, building skill packs, and replacing any
+ built-in component through the config.yaml use: field.
+
+
+DeerFlow's pluggable architecture means most parts of the system can be replaced or extended without forking the core. This page maps the extension points and explains how to use each one.
+
+## Custom middlewares
+
+Middlewares are the primary extension point for adding behavior to the Lead Agent. They wrap every LLM turn and can read and modify the agent's state before or after the model call.
+
+To add a custom middleware:
+
+1. Implement the `AgentMiddleware` interface from `langchain.agents.middleware`.
+2. Pass your middleware to the `custom_middlewares` parameter when building the agent.
+
+```python
+from langchain.agents.middleware import AgentMiddleware
+from deerflow.agents.thread_state import ThreadState
+
+class AuditMiddleware(AgentMiddleware):
+ async def on_start(self, state: ThreadState, config):
+ # Runs before each model call
+ print(f"[audit] turn starts: {len(state.messages)} messages in context")
+ return state, config
+
+ async def on_end(self, state: ThreadState, config):
+ # Runs after each model call
+ print(f"[audit] turn ends: last message type = {state.messages[-1].type}")
+ return state, config
+```
+
+Custom middlewares are injected into the chain immediately before `ClarificationMiddleware`, which always runs last.
+
+## Custom tools
+
+Add new tools to the agent by registering them in `config.yaml` under `tools:`:
+
+```yaml
+tools:
+ - use: mypackage.tools:my_custom_tool
+ api_key: $MY_TOOL_API_KEY
+```
+
+Your tool must be a LangChain `BaseTool` or a function decorated with `@tool`. It will be instantiated using the `use:` class path and any additional fields from the config entry.
+
+For community-style tools, the pattern is a module-level function or class that returns a `BaseTool`:
+
+```python
+# mypackage/tools.py
+from langchain_core.tools import tool
+
+@tool
+def my_custom_tool(query: str) -> str:
+ """Search my custom data source."""
+ return do_search(query)
+```
+
+## Custom sandbox provider
+
+The sandbox can be replaced by implementing the `SandboxProvider` interface:
+
+```python
+from deerflow.sandbox.sandbox_provider import SandboxProvider
+from deerflow.sandbox.sandbox import Sandbox
+
+class MyCustomSandboxProvider(SandboxProvider):
+ def acquire(self, thread_id: str | None = None) -> str:
+ # Return a sandbox_id
+ ...
+
+ def get(self, sandbox_id: str) -> Sandbox | None:
+ # Return the sandbox instance for this id
+ ...
+
+ def release(self, sandbox_id: str) -> None:
+ # Cleanup
+ ...
+```
+
+Then reference it in `config.yaml`:
+
+```yaml
+sandbox:
+ use: mypackage.sandbox:MyCustomSandboxProvider
+```
+
+## Custom memory storage
+
+Replace the file-based memory with any persistent store by implementing `MemoryStorage`:
+
+```python
+from deerflow.agents.memory.storage import MemoryStorage
+from typing import Any
+
+class RedisMemoryStorage(MemoryStorage):
+ def load(self, agent_name: str | None = None) -> dict[str, Any]:
+ ...
+
+ def reload(self, agent_name: str | None = None) -> dict[str, Any]:
+ ...
+
+ def save(self, memory_data: dict[str, Any], agent_name: str | None = None) -> bool:
+ ...
+```
+
+Configure it in `config.yaml`:
+
+```yaml
+memory:
+ storage_class: mypackage.storage:RedisMemoryStorage
+```
+
+## Custom skills
+
+Skills are the easiest extension point. Create a directory under `skills/custom/your-skill-name/` with a `SKILL.md` file. The skill is discovered automatically on the next `load_skills()` call.
+
+See [Skills](/docs/harness/skills) for the full directory structure and `SKILL.md` format.
+
+## Custom models
+
+Any LangChain-compatible chat model can be used by specifying it in the `use:` field:
+
+```yaml
+models:
+ - name: my-custom-model
+ use: mypackage.models:MyCustomChatModel
+ # Any extra fields are passed as kwargs to the constructor
+ base_url: http://my-model-server:8080
+ api_key: $MY_MODEL_API_KEY
+```
+
+The model class must implement the LangChain `BaseChatModel` interface.
+
+## Custom checkpointer
+
+Thread state persistence can use any LangGraph-compatible checkpointer:
+
+```yaml
+checkpointer:
+ type: sqlite
+ connection_string: ./my-checkpoints.db
+```
+
+For custom backends, implement the LangGraph `BaseCheckpointSaver` interface and configure it programmatically when initializing the `DeerFlowClient`.
+
+## Guardrails
+
+Add pre-execution authorization for tool calls through the `guardrails:` config:
+
+```yaml
+guardrails:
+ enabled: true
+ provider:
+ use: deerflow.guardrails.builtin:AllowlistProvider
+ config:
+ denied_tools: ["bash", "write_file"]
+```
+
+For custom guardrail logic, implement a class with `evaluate()` and `aevaluate()` methods and reference it via `use:`.
+
+
+
+
+
diff --git a/frontend/src/content/en/harness/design-principles.mdx b/frontend/src/content/en/harness/design-principles.mdx
index bae1cc217b..e741f6d744 100644
--- a/frontend/src/content/en/harness/design-principles.mdx
+++ b/frontend/src/content/en/harness/design-principles.mdx
@@ -1,3 +1,126 @@
+---
+title: Design Principles
+description: Understanding the design principles behind DeerFlow Harness helps you use it effectively, extend it confidently, and reason about how your agents will behave in production.
+---
+
+import { Callout, Cards } from "nextra/components";
+
# Design Principles
-TBD
+
+ DeerFlow is built around one central idea: agent behavior should be composed
+ from small, observable, replaceable pieces — not hardcoded into a fixed
+ workflow graph.
+
+
+Understanding the design principles behind DeerFlow Harness helps you use it effectively, extend it confidently, and reason about how your agents will behave in production.
+
+## Why a harness, not a framework
+
+A framework gives you abstractions and building blocks. You assemble the parts and write the glue code that connects them.
+
+A **harness** goes further. It packages an opinionated, ready-to-run runtime so that agents can do real work without you rebuilding the same infrastructure every time.
+
+DeerFlow is a harness because it bundles:
+
+- a lead agent with tool routing,
+- a middleware chain that wraps every LLM turn,
+- sandboxed execution for files and commands,
+- skills that load specialized capabilities on demand,
+- subagents for delegated parallel work,
+- memory for cross-session continuity, and
+- a configuration system that controls all of it.
+
+You do not need to design the orchestration layer from scratch. The harness is the orchestration layer.
+
+## Long-horizon tasks are the primary case
+
+DeerFlow is designed for tasks that require more than a single prompt-response exchange. A useful long-horizon agent must:
+
+1. make a plan,
+2. call tools in sequence,
+3. inspect and modify files,
+4. recover when something fails,
+5. delegate work to subagents when the task is too broad, and
+6. return a concrete artifact at the end.
+
+Every architectural decision in DeerFlow is evaluated against this use case. Short, stateless exchanges are easy. Long, multi-step workflows under real-world pressure are the target.
+
+## Middleware chain over inheritance
+
+DeerFlow does not ask you to subclass an agent or override methods to change its behavior. Instead, it uses a **middleware chain** that wraps every LLM turn.
+
+Each middleware is a small, focused plugin that can inspect or modify the agent's state before and after the model call. The lead agent's behavior is entirely determined by which middlewares are active.
+
+This design has several benefits:
+
+- Individual behaviors (memory, summarization, clarification, loop detection) are isolated and testable independently.
+- The chain can be extended without touching the agent's core logic.
+- Each middleware's effect is visible and auditable because it only touches the state it declares.
+
+See the [Middlewares](/docs/harness/middlewares) page for the full list and configuration.
+
+## Skills provide specialization without contamination
+
+A **skill** is a task-oriented capability package. It contains instructions, workflows, best practices, and any tools or resources that make the agent effective at a specific class of work.
+
+The key design decision is that skills are loaded **on demand**. The base agent stays general. When a task requires deep research, the research skill is loaded. When a task requires data analysis, the analysis skill is loaded.
+
+This matters because it keeps the base agent's context clean. A specialized prompt for writing academic papers does not pollute a session focused on coding. Skills inject their content exactly when relevant and no further.
+
+Skills also make the system extensible. Adding a new capability to DeerFlow means writing a new skill pack, not modifying the agent core.
+
+## Sandbox is the execution environment
+
+DeerFlow gives agents a **sandbox**: an isolated workspace where they can read files, write outputs, run commands, and produce artifacts.
+
+This turns the agent from a text generator into a system that can do work. Instead of only describing what code to write, the agent can write it, run it, and verify the result.
+
+Isolation is important because execution should be reproducible and controllable. The sandbox is the reason DeerFlow can support genuine action rather than only conversation.
+
+Two modes are available:
+
+- **LocalSandbox**: commands run directly on the host. Suitable for trusted, single-user local workflows.
+- **Container-based sandbox**: commands run in an isolated container (Docker or Apple Container). Suitable for multi-user environments and production deployments.
+
+## Context engineering keeps long tasks tractable
+
+Context pressure is the primary challenge for long-horizon agents. If everything accumulates in the context window indefinitely, the agent becomes slower, noisier, and less reliable.
+
+DeerFlow addresses this through **context engineering** — deliberate control of what the agent sees, remembers, and ignores at each step:
+
+- **Summarization**: when the conversation grows too long, older turns are summarized and replaced. The agent retains the meaning without the bulk.
+- **Scoped subagent context**: when work is delegated to a subagent, that subagent receives only the information it needs for its piece of the task, not the full parent history.
+- **External working memory**: files and artifacts produced during a task live on disk, not in the context window. The agent references them when needed.
+- **Memory injection**: cross-session facts are injected into the system prompt at a controlled token budget.
+
+This is one of the most important ideas in DeerFlow. Good agent behavior is not only about a stronger model. It is also about giving the model the right working set at the right time.
+
+## Configuration drives behavior
+
+All meaningful behaviors in DeerFlow are controlled through `config.yaml`. The system is designed so that operators can change how the agent behaves — which models to use, whether summarization is active, how subagents are limited, what tools are available — without touching code.
+
+This design principle has three implications:
+
+1. **Reproducibility**: a config file is a complete description of the agent's behavior at a point in time.
+2. **Deployability**: the same code runs differently in different environments because the config is different.
+3. **Auditability**: what the agent can and cannot do is visible in one place.
+
+Environment variable interpolation (`api_key: $OPENAI_API_KEY`) keeps secrets out of committed config files while preserving the same structure.
+
+## Summary
+
+| Principle | What it means in practice |
+| --------------------------- | -------------------------------------------------------------- |
+| Harness, not framework | Ready-to-run runtime with all the infrastructure already wired |
+| Long-horizon first | Architecture assumes multi-step, multi-tool, multi-turn tasks |
+| Middleware over inheritance | Behavior is composed from small, isolated plugins |
+| Skills for specialization | Domain capability injected on demand, keeping the base clean |
+| Sandbox for execution | Isolated workspace for real file and command work |
+| Context engineering | Active management of what the agent sees to stay effective |
+| Config-driven | All key behaviors are controlled through `config.yaml` |
+
+
+
+
+
diff --git a/frontend/src/content/en/harness/index.mdx b/frontend/src/content/en/harness/index.mdx
index 02c9e46f3f..ea0c098ecc 100644
--- a/frontend/src/content/en/harness/index.mdx
+++ b/frontend/src/content/en/harness/index.mdx
@@ -1,3 +1,8 @@
+---
+title: Install DeerFlow Harness
+description: The DeerFlow Harness is the Python SDK and runtime foundation for building your own Super Agent systems.
+---
+
import { Callout, Cards } from "nextra/components";
# Install DeerFlow Harness
diff --git a/frontend/src/content/en/harness/integration-guide.mdx b/frontend/src/content/en/harness/integration-guide.mdx
index c81251a8b0..61fffc9af4 100644
--- a/frontend/src/content/en/harness/integration-guide.mdx
+++ b/frontend/src/content/en/harness/integration-guide.mdx
@@ -1,3 +1,180 @@
+---
+title: Integration Guide
+description: DeerFlow Harness is not only a standalone application. It is a Python library you can import and use inside your own backend, API server, automation system, or multi-agent orchestrator.
+---
+
+import { Callout, Cards } from "nextra/components";
+
# Integration Guide
-TBD
+
+ DeerFlow Harness can be embedded into any Python application. This guide
+ covers the integration patterns for using DeerFlow as a library inside your
+ own system.
+
+
+DeerFlow Harness is not only a standalone application. It is a Python library you can import and use inside your own backend, API server, automation system, or multi-agent orchestrator.
+
+## Embedding DeerFlowClient
+
+The primary integration point is `DeerFlowClient`. It wraps the LangGraph runtime and exposes a clean API for sending messages and streaming responses from any Python application.
+
+```python
+from deerflow.client import DeerFlowClient
+from deerflow.config import load_config
+
+# Load configuration (reads config.yaml or DEER_FLOW_CONFIG_PATH)
+load_config()
+
+client = DeerFlowClient()
+```
+
+The client is thread-safe and designed to be instantiated once and reused across requests.
+
+## Async streaming
+
+The recommended integration pattern is async streaming. This gives you real-time access to each token and event as the agent produces it:
+
+```python
+import asyncio
+
+async def run_agent(thread_id: str, user_message: str):
+ async for event in client.astream(
+ thread_id=thread_id,
+ message=user_message,
+ config={
+ "configurable": {
+ "model_name": "gpt-4o",
+ "subagent_enabled": True,
+ }
+ },
+ ):
+ # Process each streaming event
+ yield event
+
+# In a FastAPI handler:
+# from fastapi.responses import StreamingResponse
+# return StreamingResponse(run_agent(thread_id, message), media_type="text/event-stream")
+```
+
+## Non-streaming invocation
+
+For batch processing or when you only need the final result:
+
+```python
+async def run_agent_sync(thread_id: str, user_message: str) -> dict:
+ result = await client.ainvoke(
+ thread_id=thread_id,
+ message=user_message,
+ )
+ return result
+```
+
+## Thread management
+
+Threads represent persistent conversations. Use unique thread IDs to isolate different user sessions:
+
+```python
+import uuid
+
+# New conversation
+thread_id = str(uuid.uuid4())
+
+# Continuing an existing conversation (same thread_id)
+# The agent will see the full history if a checkpointer is configured
+await client.ainvoke(thread_id=existing_thread_id, message="Follow up question")
+```
+
+## Custom per-agent configuration
+
+Build domain-specific agents by creating named agent configs and passing the `agent_name` at runtime:
+
+```python
+# agents/research-assistant/config.yaml must exist with skills and tool config
+
+result = await client.ainvoke(
+ thread_id=thread_id,
+ message=user_message,
+ config={
+ "configurable": {
+ "agent_name": "research-assistant",
+ "model_name": "gpt-4o",
+ }
+ },
+)
+```
+
+## Integrating with FastAPI
+
+DeerFlow Gateway is itself a FastAPI application. You can mount it as a sub-application or router:
+
+```python
+from fastapi import FastAPI
+from deerflow.config import load_config
+
+load_config()
+
+app = FastAPI()
+
+# Mount the DeerFlow gateway router
+from deerflow.app.gateway.main import app as gateway_app
+app.mount("/deerflow", gateway_app)
+```
+
+Or use `DeerFlowClient` directly in your own FastAPI routes with streaming:
+
+```python
+from fastapi import FastAPI
+from fastapi.responses import StreamingResponse
+from deerflow.client import DeerFlowClient
+
+app = FastAPI()
+client = DeerFlowClient()
+
+@app.post("/chat/{thread_id}")
+async def chat(thread_id: str, body: dict):
+ async def generate():
+ async for event in client.astream(thread_id=thread_id, message=body["message"]):
+ yield f"data: {event}\n\n"
+ return StreamingResponse(generate(), media_type="text/event-stream")
+```
+
+## Integrating with LangGraph
+
+DeerFlow Harness is built on LangGraph. The Lead Agent is a standard LangGraph graph. You can compose it with your own LangGraph nodes and graphs:
+
+```python
+from deerflow.agents.lead_agent.agent import make_lead_agent
+from langgraph.graph import StateGraph
+
+# Access the underlying LangGraph agent factory
+agent = make_lead_agent(config)
+```
+
+## Configuration in embedded mode
+
+When embedded in another application, set the config path explicitly to avoid ambiguity:
+
+```python
+import os
+os.environ["DEER_FLOW_CONFIG_PATH"] = "/path/to/my-deerflow-config.yaml"
+
+from deerflow.config import load_config
+load_config()
+```
+
+Or pass the path directly:
+
+```python
+from deerflow.config import load_config
+load_config(config_path="/path/to/my-deerflow-config.yaml")
+```
+
+## MCP server integration
+
+DeerFlow can expose its agent as an MCP server, allowing other MCP-compatible systems to call it as a tool. Refer to the DeerFlow repository for MCP server integration examples.
+
+
+
+
+
diff --git a/frontend/src/content/en/harness/lead-agent.mdx b/frontend/src/content/en/harness/lead-agent.mdx
new file mode 100644
index 0000000000..ee89888c0a
--- /dev/null
+++ b/frontend/src/content/en/harness/lead-agent.mdx
@@ -0,0 +1,156 @@
+---
+title: Lead Agent
+description: The Lead Agent is the central executor in a DeerFlow thread. Every conversation, task, and workflow flows through it. Understanding how it works helps you configure it effectively and extend it when needed.
+---
+
+import { Callout, Cards, Steps } from "nextra/components";
+
+# Lead Agent
+
+
+ The Lead Agent is the primary reasoning and orchestration unit in every
+ DeerFlow thread. It decides what to do, calls tools, delegates to subagents,
+ and returns artifacts.
+
+
+The Lead Agent is the central executor in a DeerFlow thread. Every conversation, task, and workflow flows through it. Understanding how it works helps you configure it effectively and extend it when needed.
+
+## What the Lead Agent does
+
+The Lead Agent is responsible for:
+
+- receiving user messages and maintaining conversation state,
+- reasoning about what to do next (planning, tool selection, delegation),
+- calling tools — built-in, community, MCP, or skill tools,
+- delegating subtasks to subagents via the `task` tool,
+- managing artifacts (files, outputs, deliverables),
+- updating the todo list in plan mode, and
+- returning final responses or artifacts to the user.
+
+The Lead Agent does not hardcode a specific workflow. It uses the model's reasoning to adapt to whatever task the user provides, guided by the system prompt and the skills currently in scope.
+
+## Runtime foundation
+
+The Lead Agent is built on **LangGraph** and **LangChain Agent** primitives. Specifically:
+
+- [`create_agent`](https://python.langchain.com/docs/concepts/agents/) from `langchain.agents` wraps the LLM into a tool-calling agent loop.
+- LangGraph manages the `ThreadState` and provides the checkpointing, streaming, and graph execution model.
+- A **middleware chain** wraps every turn of the agent loop, providing cross-cutting capabilities like memory, summarization, and clarification.
+
+## Execution flow
+
+
+
+### Receive message
+
+The user message arrives and is added to `ThreadState.messages`. The `ThreadState` holds the full conversation history, any active todo list, accumulated artifacts, and runtime metadata.
+
+### Middleware pre-processing
+
+Before the model is called, each active middleware has a chance to modify the state. For example, the `MemoryMiddleware` injects persisted memory facts into the system prompt, and the `SummarizationMiddleware` may condense old messages if the token budget is exceeded.
+
+### LLM reasoning
+
+The model receives the current messages (including system prompt with active skill instructions) and produces either a direct reply or one or more tool call requests.
+
+### Tool execution
+
+If tool calls are requested, they are dispatched to the appropriate handlers — sandbox tools for file and command work, community tools for web access, or the `task` tool for subagent delegation.
+
+### Middleware post-processing
+
+After tool results are returned and before the next model call, middlewares run again. The `TitleMiddleware` may generate a thread title on the first exchange, and the `TodoMiddleware` may update the task list.
+
+### Loop or respond
+
+If the model needs more information (e.g., a tool returned partial results), the loop continues. When the model decides the task is complete, it produces a final message and the loop ends.
+
+### State update
+
+`ThreadState` is updated with new messages, artifacts, and memory queues. If a checkpointer is configured, the state is persisted.
+
+
+
+## Model selection
+
+The Lead Agent resolves which model to use at runtime using the following priority order:
+
+1. `model_name` (or `model`) from the per-request configuration, if provided and valid.
+2. The `model` field of the active custom agent's config, if an agent is specified.
+3. The first model in the `models:` list in `config.yaml` (the global default).
+
+If the requested model name is not found in the config, the system falls back to the default model and logs a warning.
+
+```yaml
+models:
+ - name: my-primary-model
+ use: langchain_openai:ChatOpenAI
+ model: gpt-4o
+ api_key: $OPENAI_API_KEY
+ request_timeout: 600.0
+ max_retries: 2
+ supports_vision: true
+
+ - name: my-fast-model
+ use: langchain_openai:ChatOpenAI
+ model: gpt-4o-mini
+ api_key: $OPENAI_API_KEY
+```
+
+The first entry (`my-primary-model`) becomes the default. Any request that does not specify a model, or specifies an unknown model name, will use it.
+
+## Thinking mode
+
+If the model supports extended thinking (e.g., DeepSeek Reasoner, Doubao with thinking enabled, Anthropic Claude with thinking), the Lead Agent can run in **thinking mode**. In this mode, the model's internal reasoning steps are visible in the response stream.
+
+Thinking mode is controlled per-request through the `thinking_enabled` flag. If thinking is enabled but the configured model does not support it, the system falls back gracefully and logs a warning.
+
+```yaml
+models:
+ - name: deepseek-v3
+ use: deerflow.models.patched_deepseek:PatchedChatDeepSeek
+ model: deepseek-reasoner
+ api_key: $DEEPSEEK_API_KEY
+ supports_thinking: true
+ when_thinking_enabled:
+ extra_body:
+ thinking:
+ type: enabled
+ when_thinking_disabled:
+ extra_body:
+ thinking:
+ type: disabled
+```
+
+## Plan mode
+
+When `is_plan_mode` is set to `true` in the request configuration, the `TodoMiddleware` is activated. The agent then maintains a structured task list, marking items as `in_progress`, `completed`, or `pending` as it works through a complex task. This provides visibility into the agent's progress for the user.
+
+Plan mode is appropriate for complex, multi-step tasks where showing incremental progress is valuable. For simple requests, it is better left disabled to avoid unnecessary overhead.
+
+## Custom agents
+
+The same Lead Agent runtime powers both the default agent and any custom agents you create. A custom agent differs only in:
+
+- its **name** (ASCII slug, auto-derived from `display_name`),
+- its **system prompt** or agent-specific instructions,
+- which **skills** it has access to,
+- which **tool groups** it can use, and
+- which **model** it defaults to.
+
+Custom agents are created through the DeerFlow App UI or via the `/api/agents` endpoint. Their configuration is stored in `agents/{name}/config.yaml` relative to the backend directory.
+
+
+ When a custom agent is selected in a thread, the Lead Agent loads that agent's
+ config at runtime. Switching models or skills for a specific agent does not
+ require restarting the server.
+
+
+## Bootstrap mode
+
+DeerFlow includes a special **bootstrap mode** for the initial setup of custom agents. When `is_bootstrap: true` is passed in the request config, the Lead Agent runs with a minimal system prompt and only the core setup tools exposed. This is used internally to guide the first-run agent configuration flow.
+
+
+
+
+
diff --git a/frontend/src/content/en/harness/mcp.mdx b/frontend/src/content/en/harness/mcp.mdx
new file mode 100644
index 0000000000..0e43aa235f
--- /dev/null
+++ b/frontend/src/content/en/harness/mcp.mdx
@@ -0,0 +1,116 @@
+---
+title: MCP Integration
+description: The **Model Context Protocol (MCP)** is an open standard for connecting language models to external tools and data sources. DeerFlow's MCP integration allows you to extend the agent with any tool server that implements the MCP protocol — without modifying the harness itself.
+---
+
+import { Callout, Cards, Steps } from "nextra/components";
+
+# MCP Integration
+
+
+ Model Context Protocol (MCP) lets DeerFlow connect to any external tool
+ server. Once connected, MCP tools are available to the Lead Agent exactly like
+ built-in tools.
+
+
+The **Model Context Protocol (MCP)** is an open standard for connecting language models to external tools and data sources. DeerFlow's MCP integration allows you to extend the agent with any tool server that implements the MCP protocol — without modifying the harness itself.
+
+## Configuration
+
+MCP servers are configured in `extensions_config.json`, a file separate from `config.yaml`. This separation allows MCP and skill configurations to be managed independently and updated at runtime through the Gateway API.
+
+The default location is the project root (same directory as `config.yaml`). The path is determined by `ExtensionsConfig.resolve_config_path()`.
+
+```json
+{
+ "mcpServers": {
+ "my-server": {
+ "command": "npx",
+ "args": ["-y", "@my-org/my-mcp-server"],
+ "enabled": true
+ },
+ "filesystem": {
+ "command": "npx",
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"],
+ "enabled": true
+ },
+ "sqlite": {
+ "command": "uvx",
+ "args": ["mcp-server-sqlite", "--db-path", "/path/to/db.sqlite"],
+ "enabled": false
+ }
+ }
+}
+```
+
+Each server entry supports:
+
+- `command`: the executable to run (e.g., `npx`, `uvx`, `python`)
+- `args`: command arguments as an array
+- `enabled`: whether the server is active (can be toggled without removing the entry)
+- `env`: optional environment variables injected into the server process
+
+## How tools are loaded
+
+
+
+### Startup initialization
+
+When the DeerFlow server starts, `initialize_mcp_tools()` is called. This connects to all enabled MCP servers, retrieves their tool schemas, and caches the results.
+
+### Lazy initialization fallback
+
+If the server starts before MCP tools are initialized (e.g., in LangGraph Studio), `get_cached_mcp_tools()` performs lazy initialization on the first tool call.
+
+### Cache invalidation
+
+The MCP tools cache tracks the modification time (`mtime`) of `extensions_config.json`. When the file changes — for example, when a server is enabled or disabled through the Gateway API — the cache is marked stale and tools are reloaded on the next request.
+
+This means MCP server changes take effect without restarting the DeerFlow server.
+
+### Tool availability
+
+Once loaded, MCP tools appear in the Lead Agent's tool list alongside built-in and community tools. The agent selects and calls them using the same mechanism as any other tool.
+
+
+
+## Tool search integration
+
+When many MCP servers expose a large number of tools, loading all of them into the agent's context at once can increase token usage and reduce tool selection accuracy.
+
+Enable **tool search** to load MCP tools on demand instead:
+
+```yaml
+# config.yaml
+tool_search:
+ enabled: true
+```
+
+With tool search enabled, MCP tools are listed by name in the system prompt but not included in the full tool schema. The agent discovers them using the `tool_search` built-in tool and loads only the ones it needs for a given task.
+
+## OAuth support
+
+Some MCP servers require OAuth authentication. DeerFlow's `mcp/oauth.py` handles the OAuth flow for servers that declare OAuth requirements in their capability headers.
+
+When an OAuth-protected MCP server is connected, DeerFlow will:
+
+1. Detect the OAuth requirement from the server's capability headers
+2. Build the appropriate authorization headers using `get_initial_oauth_headers()`
+3. Wrap tool calls with an OAuth interceptor via `build_oauth_tool_interceptor()`
+
+The OAuth flow is transparent to the Lead Agent — it simply calls the tool, and DeerFlow handles the authentication.
+
+## Managing MCP servers
+
+MCP servers can be managed in several ways:
+
+- **Through the DeerFlow App UI**: the extensions panel shows connected MCP servers and lets you enable/disable them.
+- **Through the Gateway API**: `POST /api/extensions/mcp/{name}/enable` and `/disable`.
+- **By editing `extensions_config.json` directly**: useful for scripted or programmatic configuration.
+
+Changes are picked up automatically due to the file mtime-based cache invalidation.
+
+
+
+
+
diff --git a/frontend/src/content/en/harness/memory.mdx b/frontend/src/content/en/harness/memory.mdx
index 30fd1060f9..4904c54bb9 100644
--- a/frontend/src/content/en/harness/memory.mdx
+++ b/frontend/src/content/en/harness/memory.mdx
@@ -1,3 +1,124 @@
+---
+title: Memory
+description: Memory is a runtime feature of the DeerFlow Harness. It is not a simple conversation log — it is a structured store of facts and context summaries that persist across separate sessions and inform the agent's behavior in future conversations.
+---
+
+import { Callout, Cards } from "nextra/components";
+
# Memory
-TBD
+
+ Memory lets DeerFlow carry useful information across sessions. The agent
+ remembers user preferences, project context, and recurring facts so it can
+ give better responses without starting from zero every time.
+
+
+Memory is a runtime feature of the DeerFlow Harness. It is not a simple conversation log — it is a structured store of facts and context summaries that persist across separate sessions and inform the agent's behavior in future conversations.
+
+## What memory stores
+
+The memory store holds several categories of information:
+
+- **Work context**: summaries of ongoing projects, goals, and recurring topics the user works on.
+- **Personal context**: preferences, communication style, and other user-specific details the agent has learned.
+- **Top of mind**: the most recent focus areas and active tasks.
+- **History**: recent months' context, earlier background, and long-term facts.
+- **Facts**: discrete, specific facts the agent has extracted from conversations (e.g., preferred tools, team names, project constraints).
+
+Each category is updated over time as the agent learns from ongoing conversations.
+
+## How it works
+
+Memory is managed by `MemoryMiddleware`, which runs on every Lead Agent turn:
+
+1. **Injection**: at the start of each conversation, the agent's current memory is injected into the system prompt at a controlled token budget (`max_injection_tokens`).
+2. **Learning**: after a conversation, a background job extracts new facts and updates the relevant memory categories. Updates are debounced by `debounce_seconds` to batch rapid changes.
+3. **Per-agent memory**: when a custom agent is active, its memory is stored separately from the global memory. This keeps different agents' knowledge isolated.
+
+## Configuration
+
+```yaml
+memory:
+ enabled: true
+
+ # Storage path for the global memory file.
+ # Default: {base_dir}/memory.json (resolves to backend/.deer-flow/memory.json)
+ # Absolute paths are used as-is.
+ # Relative paths are resolved against base_dir (not the backend working directory).
+ storage_path: memory.json
+
+ # Storage class (default: file-based JSON storage)
+ storage_class: deerflow.agents.memory.storage.FileMemoryStorage
+
+ # Seconds to wait before processing queued memory updates (debounce)
+ debounce_seconds: 30
+
+ # Model for memory update extraction (null = use default model)
+ model_name: null
+
+ # Maximum number of facts to store
+ max_facts: 100
+
+ # Minimum confidence score required to store a fact (0.0–1.0)
+ fact_confidence_threshold: 0.7
+
+ # Whether to inject memory into the system prompt
+ injection_enabled: true
+
+ # Maximum tokens to use for memory injection into system prompt
+ max_injection_tokens: 2000
+```
+
+## Global vs per-agent memory
+
+DeerFlow supports two levels of memory:
+
+- **Global memory**: stored at `{base_dir}/memory.json`. Used when no specific agent is active or when the agent has no per-agent memory file.
+- **Per-agent memory**: stored at `{base_dir}/agents/{agent_name}/memory.json`. Used when a custom agent is active, keeping that agent's learned knowledge separate.
+
+The `MemoryMiddleware` automatically selects the correct memory file based on the active `agent_name` in the request configuration.
+
+Agent names used for memory storage are validated against `AGENT_NAME_PATTERN` to ensure filesystem safety.
+
+## Storage location
+
+By default, memory files are stored under the backend base directory:
+
+- Base directory: `backend/.deer-flow/`
+- Global memory: `backend/.deer-flow/memory.json`
+- Per-agent memory: `backend/.deer-flow/agents/{agent_name}/memory.json`
+
+You can change the storage path with the `storage_path` field. Relative paths are resolved against the base directory. Use an absolute path to store memory in a custom location.
+
+## Custom storage backend
+
+The `storage_class` field allows you to replace the default file-based storage with a custom implementation. Any class that extends `MemoryStorage` and implements `load()`, `reload()`, and `save()` methods can be used:
+
+```yaml
+memory:
+ storage_class: mypackage.storage.RedisMemoryStorage
+```
+
+If the configured class cannot be loaded, the system falls back to the default `FileMemoryStorage` and logs an error.
+
+## Disabling memory
+
+To disable memory entirely:
+
+```yaml
+memory:
+ enabled: false
+```
+
+To keep memory storage but prevent injection into the system prompt:
+
+```yaml
+memory:
+ enabled: true
+ injection_enabled: false
+```
+
+
+
+
+
diff --git a/frontend/src/content/en/harness/middlewares.mdx b/frontend/src/content/en/harness/middlewares.mdx
new file mode 100644
index 0000000000..70c499925f
--- /dev/null
+++ b/frontend/src/content/en/harness/middlewares.mdx
@@ -0,0 +1,219 @@
+---
+title: Middlewares
+description: Every time the Lead Agent calls the LLM, it runs through a **middleware chain** before and after the model call. Middlewares can read and modify the agent's state, inject content into the system prompt, intercept tool calls, and react to model outputs.
+---
+
+import { Callout } from "nextra/components";
+
+# Middlewares
+
+
+ Middlewares wrap every LLM turn in the Lead Agent. They are the primary
+ extension point for adding cross-cutting behaviors like memory, summarization,
+ clarification, and token tracking.
+
+
+Every time the Lead Agent calls the LLM, it runs through a **middleware chain** before and after the model call. Middlewares can read and modify the agent's state, inject content into the system prompt, intercept tool calls, and react to model outputs.
+
+This design keeps the agent core simple and stable while allowing rich, composable behaviors to be layered in.
+
+## How the chain works
+
+The middleware chain is built once per agent invocation, based on the current configuration and request parameters. The middlewares run in a defined order:
+
+1. Runtime middlewares (error handling, thread data, uploads, dangling tool call patching)
+2. `SummarizationMiddleware` — context compression (if enabled)
+3. `TodoMiddleware` — task list management (plan mode only)
+4. `TokenUsageMiddleware` — token tracking (if enabled)
+5. `TitleMiddleware` — automatic thread title generation
+6. `MemoryMiddleware` — cross-session memory injection and queuing
+7. `ViewImageMiddleware` — image details injection (if model supports vision)
+8. `DeferredToolFilterMiddleware` — hides deferred tool schemas (if tool search enabled)
+9. `SubagentLimitMiddleware` — limits parallel subagent calls (if subagents enabled)
+10. `LoopDetectionMiddleware` — breaks repetitive tool call loops
+11. Custom middlewares (if any)
+12. `ClarificationMiddleware` — intercepts clarification requests (always last)
+
+The ordering is significant. Summarization runs early to reduce context before other processing. Clarification always runs last so it can intercept after all other middlewares have had their turn.
+
+## Middleware reference
+
+### ClarificationMiddleware
+
+Intercepts clarification tool calls and converts them into proper user-facing requests for additional information. When the model decides it needs to ask the user something before proceeding, this middleware surfaces that request.
+
+**Configuration**: controlled by `guardrails.clarification` settings.
+
+---
+
+### LoopDetectionMiddleware
+
+Detects when the agent is making the same tool call repeatedly without making progress. When a loop is detected, the middleware intervenes to break the cycle and prevents the agent from burning turns indefinitely.
+
+**Configuration**: built-in, no user configuration.
+
+---
+
+### MemoryMiddleware
+
+Reads persisted memory facts at the start of each conversation and injects them into the system prompt. After a conversation ends, queues a background update to incorporate any new information into the memory store.
+
+**Configuration**: see the [Memory](/docs/harness/memory) page and the `memory:` section in `config.yaml`.
+
+```yaml
+memory:
+ enabled: true
+ injection_enabled: true
+ max_injection_tokens: 2000
+ debounce_seconds: 30
+```
+
+---
+
+### SubagentLimitMiddleware
+
+Limits the number of parallel subagent task calls the agent can make in a single turn. This prevents the agent from spawning an unbounded number of concurrent subagents.
+
+**Configuration**: `subagent_enabled` and `max_concurrent_subagents` in the per-request config.
+
+---
+
+### TitleMiddleware
+
+Automatically generates a title for the thread after the first exchange. The title is derived from the user's first message and the agent's response.
+
+**Configuration**: `title:` section in `config.yaml`.
+
+```yaml
+title:
+ enabled: true
+ max_words: 6
+ max_chars: 60
+ model_name: null # use default model
+```
+
+---
+
+### TodoMiddleware
+
+When plan mode is active, maintains a structured task list visible to the user. The agent uses the `write_todos` tool to mark tasks as `pending`, `in_progress`, or `completed` as it works through a complex objective.
+
+**Activation**: enabled automatically when `is_plan_mode: true` is set in the request configuration. No `config.yaml` entry required.
+
+---
+
+### TokenUsageMiddleware
+
+Tracks LLM token consumption per model call and logs it at the `info` level. Useful for monitoring costs and understanding where tokens are going in long tasks.
+
+**Configuration**: `token_usage:` section in `config.yaml`.
+
+```yaml
+token_usage:
+ enabled: false
+```
+
+---
+
+### SandboxAuditMiddleware
+
+Audits sandbox operations performed during the agent's execution. Provides a record of what files were read, written, and what commands were run.
+
+**Configuration**: built-in runtime middleware, always active when a sandbox is available.
+
+---
+
+### SummarizationMiddleware
+
+When the conversation grows long, summarizes older messages to reduce context size. The summary is injected back into the conversation in place of the original messages, preserving meaning without the full token cost.
+
+**Configuration**: `summarization:` section in `config.yaml`. See detailed configuration below.
+
+---
+
+### ViewImageMiddleware
+
+When the current model supports vision (`supports_vision: true`), this middleware intercepts `view_image` tool calls and injects the image content directly into the model's context so it can be analyzed.
+
+**Activation**: automatically enabled when the resolved model has `supports_vision: true`.
+
+---
+
+### DeferredToolFilterMiddleware
+
+When tool search is enabled, this middleware hides deferred tool schemas from the model's context. Tools are discovered lazily via the `tool_search` tool instead of being listed upfront, reducing context usage.
+
+**Configuration**: `tool_search.enabled: true` in `config.yaml`.
+
+## Summarization configuration
+
+The `SummarizationMiddleware` is one of the most impactful middlewares for long-horizon tasks. Here is the full configuration reference:
+
+```yaml
+summarization:
+ enabled: true
+
+ # Model to use for summarization (null = use default model)
+ # A lightweight model like gpt-4o-mini is recommended to reduce cost.
+ model_name: null
+
+ # Trigger conditions — summarization runs when ANY threshold is met
+ trigger:
+ - type: tokens # trigger when context exceeds N tokens
+ value: 15564
+ # - type: messages # trigger when there are more than N messages
+ # value: 50
+ # - type: fraction # trigger when context exceeds X% of model max
+ # value: 0.8
+
+ # How much recent history to keep after summarization
+ keep:
+ type: messages
+ value: 10 # keep the 10 most recent messages
+ # Alternative: keep by tokens
+ # type: tokens
+ # value: 3000
+
+ # Maximum tokens to trim when preparing messages for the summarizer
+ trim_tokens_to_summarize: 15564
+
+ # Custom summary prompt (null = use default LangChain prompt)
+ summary_prompt: null
+```
+
+**Trigger types**:
+
+- `tokens`: triggers when the total token count in the conversation exceeds `value`.
+- `messages`: triggers when the number of messages exceeds `value`.
+- `fraction`: triggers when the context reaches `value` fraction of the model's maximum input token limit.
+
+Multiple triggers can be listed; summarization runs when **any** of them fires.
+
+**Keep types**:
+
+- `messages`: keep the last `value` messages after summarization.
+- `tokens`: keep up to `value` tokens of recent history.
+- `fraction`: keep up to `value` fraction of the model's max input token limit.
+
+## Writing a custom middleware
+
+Custom middlewares can be injected into the chain for specialized use cases. A middleware must implement the `AgentMiddleware` interface from `langchain.agents.middleware`.
+
+The basic structure is:
+
+```python
+from langchain.agents.middleware import AgentMiddleware
+
+class MyMiddleware(AgentMiddleware):
+ async def on_start(self, state, config):
+ # Runs before the model call
+ # Modify state or config here
+ return state, config
+
+ async def on_end(self, state, config):
+ # Runs after the model call
+ # Inspect or modify the result
+ return state, config
+```
+
+Custom middlewares are passed to `make_lead_agent` via the `custom_middlewares` parameter in `_build_middlewares`. They are injected immediately before `ClarificationMiddleware` at the end of the chain.
diff --git a/frontend/src/content/en/harness/quick-start.mdx b/frontend/src/content/en/harness/quick-start.mdx
index 81c1028364..b253a122c7 100644
--- a/frontend/src/content/en/harness/quick-start.mdx
+++ b/frontend/src/content/en/harness/quick-start.mdx
@@ -1,3 +1,118 @@
+---
+title: Quick Start
+description: Learn how to create and run a DeerFlow agent with create_deerflow_agent, from model setup to streaming responses.
+---
+
+import { Callout, Cards, Steps } from "nextra/components";
+
# Quick Start
-TBD
+
+ This guide shows you how to build and run a DeerFlow agent in Python with
+ create_deerflow_agent.
+
+
+The fastest way to understand DeerFlow Harness is to create an agent directly in code. This quick start walks through model setup, agent creation, and streaming a response.
+
+## Prerequisites
+
+DeerFlow Harness requires Python 3.12 or later. The package is part of the `deerflow` repository under `backend/packages/harness`.
+
+If you are working from the repository clone:
+
+```bash
+cd backend
+uv sync
+```
+
+You will also need a chat model instance from the LangChain provider package you want to use.
+
+## Create your first agent
+
+
+
+### Import the factory and model
+
+```python
+from deerflow.agents import create_deerflow_agent
+from langchain_openai import ChatOpenAI
+```
+
+### Create a model
+
+```python
+model = ChatOpenAI(
+ model="gpt-4o",
+ api_key="YOUR_OPENAI_API_KEY",
+)
+```
+
+### Create an agent
+
+```python
+agent = create_deerflow_agent(model)
+```
+
+This returns a compiled LangGraph agent with DeerFlow's default middleware chain.
+
+### Stream a response
+
+```python
+for event in agent.stream(
+ {"messages": [{"role": "user", "content": "Explain what DeerFlow Harness is."}]},
+ stream_mode=["messages", "values"],
+):
+ print(event)
+```
+
+
+
+## Add tools or behavior
+
+You can customize the agent by passing tools, a system prompt, runtime features, middleware, or a checkpointer.
+
+```python
+from deerflow.agents import RuntimeFeatures, create_deerflow_agent
+
+agent = create_deerflow_agent(
+ model,
+ system_prompt="You are a concise research assistant.",
+ features=RuntimeFeatures(subagent=True, memory=False),
+ plan_mode=True,
+ name="research-agent",
+)
+```
+
+Common parameters:
+
+| Parameter | Description |
+| ------------------ | ----------------------------------------------- |
+| `tools` | Additional tools available to the agent |
+| `system_prompt` | Custom system prompt |
+| `features` | Enable or replace built-in runtime features |
+| `extra_middleware` | Insert custom middleware into the default chain |
+| `plan_mode` | Enable Todo-style task tracking |
+| `checkpointer` | Persist agent state across runs |
+| `name` | Logical agent name |
+
+## When to use DeerFlowClient instead
+
+`create_deerflow_agent()` is the low-level SDK factory when you want to work directly with the compiled agent graph.
+
+Use `DeerFlowClient` when you want the higher-level embedded app interface, such as:
+
+- thread-oriented chat helpers,
+- model / skills / memory management APIs,
+- file uploads and artifacts,
+- Gateway-like response formats.
+
+## Next steps
+
+
+
+
+
+
diff --git a/frontend/src/content/en/harness/sandbox.mdx b/frontend/src/content/en/harness/sandbox.mdx
index 318d5475fc..b1ba74b136 100644
--- a/frontend/src/content/en/harness/sandbox.mdx
+++ b/frontend/src/content/en/harness/sandbox.mdx
@@ -1,3 +1,155 @@
+---
+title: Sandbox
+description: The sandbox gives the Lead Agent a controlled environment where it can read files, write outputs, run shell commands, and produce artifacts. Without a sandbox, the agent can only generate text. With a sandbox, it can write and execute code, process data files, generate charts, and build deliverables.
+---
+
+import { Callout, Cards, Tabs } from "nextra/components";
+
# Sandbox
-TBD
+
+ The sandbox is the isolated workspace where the agent does file and
+ command-based work. It is what makes DeerFlow capable of real action, not just
+ conversation.
+
+
+The sandbox gives the Lead Agent a controlled environment where it can read files, write outputs, run shell commands, and produce artifacts. Without a sandbox, the agent can only generate text. With a sandbox, it can write and execute code, process data files, generate charts, and build deliverables.
+
+## Sandbox modes
+
+DeerFlow supports three sandbox modes. Choose the one that fits your deployment:
+
+### LocalSandbox (default)
+
+Commands run directly on the host machine's filesystem. There is no container isolation.
+
+- **Best for**: trusted, single-user local development workflows.
+- **Risk**: the agent has access to the host filesystem. Use `allow_host_bash: false` (default) to prevent arbitrary command execution.
+
+```yaml
+sandbox:
+ use: deerflow.sandbox.local:LocalSandboxProvider
+ allow_host_bash: false # default; set to true only for fully trusted workflows
+```
+
+### Container-based AIO Sandbox
+
+Commands run in an isolated container (Docker on Linux/Windows, or Apple Container on macOS). Each sandbox session gets a fresh container environment.
+
+- **Best for**: multi-user environments, production deployments, or any case where you want execution isolation.
+
+```yaml
+sandbox:
+ use: deerflow.community.aio_sandbox:AioSandboxProvider
+
+ # Optional: container image (default shown below)
+ image: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest
+
+ # Optional: max concurrent containers (default: 3, LRU eviction when exceeded)
+ replicas: 3
+
+ # Optional: container name prefix (default: deer-flow-sandbox)
+ container_prefix: deer-flow-sandbox
+
+ # Optional: idle timeout in seconds (default: 600)
+ idle_timeout: 600
+
+ # Optional: custom mounts
+ mounts:
+ - host_path: /path/on/host
+ container_path: /home/user/shared
+ read_only: false
+
+ # Optional: environment variables injected into the container
+ environment:
+ API_KEY: $MY_API_KEY
+```
+
+Install: `cd backend && uv add 'deerflow-harness[aio-sandbox]'`
+
+### Provisioner-managed Sandbox (Kubernetes)
+
+Each sandbox gets a dedicated Pod in a Kubernetes cluster, managed by the provisioner service. This provides the strongest isolation and is recommended for production environments with multiple concurrent users.
+
+```yaml
+sandbox:
+ use: deerflow.community.aio_sandbox:AioSandboxProvider
+ provisioner_url: http://provisioner:8002
+```
+
+The provisioner service is included in `docker/docker-compose-dev.yaml` and manages the Pod and Service lifecycle for each sandbox ID.
+
+## Path mappings
+
+The sandbox uses path mappings to bridge the host filesystem and the container's virtual filesystem. Two key mappings are always configured:
+
+| Host path | Container path | Access |
+| ------------------------------------------- | -------------------------------------------- | ---------- |
+| `skills/` (from `skills.path`) | `/mnt/skills` (from `skills.container_path`) | Read-only |
+| `.deer-flow/threads/{thread_id}/user-data/` | `/mnt/user-data/` | Read-write |
+
+The skills directory is always mounted read-only. Threads write their working data (uploads, outputs, intermediate files) to `/mnt/user-data/`.
+
+### Custom mounts
+
+You can add additional mounts for the local sandbox using the `mounts:` configuration:
+
+```yaml
+sandbox:
+ use: deerflow.sandbox.local:LocalSandboxProvider
+ mounts:
+ - host_path: /home/user/my-project
+ container_path: /mnt/my-project
+ read_only: true
+```
+
+
+ Custom mount `container_path` values must not conflict with reserved prefixes:
+ `/mnt/skills`, `/mnt/acp-workspace`, or `/mnt/user-data`.
+
+
+## Output truncation
+
+The sandbox tools limit output size to keep the agent's context manageable. These limits are configurable:
+
+```yaml
+sandbox:
+ use: deerflow.sandbox.local:LocalSandboxProvider
+
+ # bash uses middle-truncation (head + tail)
+ bash_output_max_chars: 20000
+
+ # read_file uses head-truncation
+ read_file_output_max_chars: 50000
+
+ # ls uses head-truncation
+ ls_output_max_chars: 20000
+```
+
+Set to `0` to disable truncation.
+
+## Security
+
+### LocalSandbox
+
+The `LocalSandbox` runs commands directly on the host. By default, the `bash` tool is **disabled** to prevent arbitrary host command execution. Enable it only for fully trusted, single-user workflows:
+
+```yaml
+sandbox:
+ allow_host_bash: true # Dangerous: grants the agent shell access to your machine
+```
+
+Even without `bash`, the agent can still read and write files through the dedicated file tools.
+
+### Container sandbox
+
+Container-based sandboxes provide filesystem and process isolation. The agent cannot see or modify the host filesystem except through explicit mounts. The provisioner-managed mode adds a further layer: each thread gets its own isolated Pod.
+
+### Audit middleware
+
+`SandboxAuditMiddleware` runs on every agent turn and records all sandbox operations. This provides an audit trail of what files were accessed and what commands were run during a session.
+
+
+
+
+
diff --git a/frontend/src/content/en/harness/skills.mdx b/frontend/src/content/en/harness/skills.mdx
index 375984c3fb..09f8b0d434 100644
--- a/frontend/src/content/en/harness/skills.mdx
+++ b/frontend/src/content/en/harness/skills.mdx
@@ -1,3 +1,165 @@
+---
+title: Skills
+description: A skill is more than a prompt. It is a self-contained capability package that can include structured instructions, step-by-step workflows, domain-specific best practices, supporting resources, and tool configurations. Skills are loaded on demand — they inject their content when a task calls for them and stay out of the context otherwise.
+---
+
+import { Callout, Cards, FileTree, Steps } from "nextra/components";
+
# Skills
-TBD
+
+ Skills are task-oriented capability packages that teach the agent how to do a
+ specific class of work. The base agent stays general; skills provide
+ specialization only when needed.
+
+
+A skill is more than a prompt. It is a self-contained capability package that can include structured instructions, step-by-step workflows, domain-specific best practices, supporting resources, and tool configurations. Skills are loaded on demand — they inject their content when a task calls for them and stay out of the context otherwise.
+
+## What a skill contains
+
+Each skill lives in its own subdirectory under `skills/public/` (or `skills/custom/` for user-created skills). The directory contains a `SKILL.md` file that defines the skill's metadata, instructions, and workflow.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+The `SKILL.md` file is the authoritative definition of the skill. It is parsed by `skills/parser.py` to extract the skill name, description, category, instructions, and any dependencies or tool requirements.
+
+## Built-in skills
+
+DeerFlow ships with the following public skills:
+
+| Skill | Description |
+| ------------------------------ | -------------------------------------------------------------------------------- |
+| `deep-research` | Multi-step research with source gathering, cross-checking, and structured output |
+| `data-analysis` | Data exploration, statistical analysis, and insight generation |
+| `chart-visualization` | Chart and graph creation from data |
+| `ppt-generation` | Presentation slide generation |
+| `image-generation` | AI image generation workflows |
+| `code-documentation` | Automated code documentation generation |
+| `newsletter-generation` | Newsletter content creation |
+| `podcast-generation` | Podcast script and outline generation |
+| `academic-paper-review` | Structured academic paper analysis |
+| `consulting-analysis` | Business consulting frameworks and analysis |
+| `systematic-literature-review` | Literature review methodology and synthesis |
+| `github-deep-research` | Repository and code deep-dive research |
+| `frontend-design` | Frontend design and UI workflow |
+| `web-design-guidelines` | Web design standards and review |
+| `video-generation` | Video content planning and generation |
+
+## Skill lifecycle
+
+
+
+### Discovery and loading
+
+`load_skills()` in `skills/loader.py` scans both `public/` and `custom/` directories under the configured skills path. It re-reads `ExtensionsConfig.from_file()` on every call, which means enabling or disabling a skill through the Gateway API takes effect immediately in the running LangGraph server without a restart.
+
+### Parsing
+
+`parser.py` reads each `SKILL.md` file and extracts structured metadata: name, description, category, instructions, and any tool or resource requirements.
+
+### Security scanning
+
+`security_scanner.py` checks skill content for potentially dangerous patterns before it is loaded into the agent's context. This step runs during skill loading to prevent malicious skill content from being injected.
+
+### Dependency installation
+
+`installer.py` handles any Python or system dependencies declared by the skill. Dependencies are installed into the runtime environment when the skill is first loaded.
+
+### Context injection
+
+When the agent is invoked with a specific skill in scope, the skill's instructions are injected into the system prompt. The agent then has access to the skill's workflow, best practices, and domain knowledge for the duration of that conversation.
+
+
+
+## Configuration
+
+The skills system is configured under `skills:` in `config.yaml`:
+
+```yaml
+skills:
+ # Path to skills directory on the host.
+ # Default: ../skills relative to the backend directory.
+ # Uncomment to customize:
+ # path: /absolute/path/to/custom/skills
+
+ # Path where skills are mounted in the sandbox container.
+ # The agent uses this path to access skill files during execution.
+ container_path: /mnt/skills
+```
+
+The `container_path` is important: it tells the agent where to find skill resources inside the sandbox. The harness automatically mounts the host skills directory to this container path.
+
+## Enabling and disabling skills
+
+Skill availability is tracked in `extensions_config.json` (separate from `config.yaml`). You can manage skill state:
+
+- **Through the DeerFlow App UI**: the skills panel lets you toggle skills on and off.
+- **Through the Gateway API**: `POST /api/extensions/skills/{name}/enable` and `/disable`.
+- **By editing `extensions_config.json` directly**.
+
+Because `load_skills()` re-reads the extensions config on every call, changes take effect immediately — no server restart required.
+
+## Restricting skills per custom agent
+
+A custom agent can be restricted to a specific subset of skills. In the agent's config (stored in `agents/{name}/config.yaml`), set a `skills` list:
+
+```yaml
+# agents/my-researcher/config.yaml
+name: my-researcher
+skills:
+ - deep-research
+ - academic-paper-review
+ # Omit all other skills
+```
+
+- **Omitted or null**: the agent loads all globally enabled skills.
+- **Empty list `[]`**: the agent has no skills.
+- **Named list**: the agent loads only those specific skills.
+
+## Skill evolution
+
+DeerFlow includes an optional **skill evolution** feature that allows the agent to autonomously create and improve skills in the `skills/custom/` directory:
+
+```yaml
+skill_evolution:
+ enabled: false # Set to true to allow agent-managed skill creation
+ moderation_model_name: null # Model for security scanning (null = use default)
+```
+
+
+ Enable skill evolution only in environments where you trust the agent's
+ outputs. Newly created skills are security-scanned before being loaded, but
+ the feature gives the agent write access to the skills directory.
+
+
+## Writing a custom skill
+
+To create a custom skill:
+
+1. Create a new directory under `skills/custom/your-skill-name/`
+2. Add a `SKILL.md` file that defines the skill's metadata and instructions
+3. The skill will be discovered automatically on the next `load_skills()` call
+
+The `SKILL.md` format follows the same structure as the built-in skills. Use one of the existing public skills as a reference for the expected format.
+
+
+
+
+
diff --git a/frontend/src/content/en/harness/subagents.mdx b/frontend/src/content/en/harness/subagents.mdx
new file mode 100644
index 0000000000..6da63cf00a
--- /dev/null
+++ b/frontend/src/content/en/harness/subagents.mdx
@@ -0,0 +1,136 @@
+---
+title: Subagents
+description: When a task is too broad for a single reasoning thread, or when parts of it can be done in parallel, the Lead Agent delegates work to **subagents**. A subagent is a self-contained agent invocation that receives a specific task, executes it, and returns the result.
+---
+
+import { Callout, Cards } from "nextra/components";
+
+# Subagents
+
+
+ Subagents are focused workers that the Lead Agent delegates subtasks to. They
+ run with isolated context, keeping the main conversation clean while handling
+ parallel or specialized work.
+
+
+When a task is too broad for a single reasoning thread, or when parts of it can be done in parallel, the Lead Agent delegates work to **subagents**. A subagent is a self-contained agent invocation that receives a specific task, executes it, and returns the result.
+
+## Why subagents matter
+
+Subagents solve two key problems in long-horizon workflows:
+
+1. **Context isolation**: a subagent only sees the information it needs for its piece of the task, not the entire parent conversation. This keeps each agent's working context focused and tractable.
+2. **Parallelism**: multiple subagents can run concurrently, allowing independent parts of a task (e.g., researching multiple topics simultaneously) to be processed in parallel.
+
+## Built-in subagents
+
+DeerFlow ships with two built-in subagents:
+
+### general-purpose
+
+A general-purpose reasoning and execution agent. Suitable for delegating complex subtasks that require multi-step reasoning, web search, file operations, and artifact production.
+
+- **Default timeout**: 900 seconds (15 minutes)
+- **Default max turns**: 160
+
+### bash
+
+A subagent specialized for command-line task execution inside the sandbox. Suitable for scripting, data processing, file transformation, and environment setup tasks.
+
+- **Default timeout**: 900 seconds (15 minutes)
+- **Default max turns**: 80
+- **Availability**: only exposed when the sandbox's `bash` tool is available (either `allow_host_bash: true` or a container sandbox is configured)
+
+## Delegation flow
+
+The Lead Agent delegates work to a subagent using the built-in `task` tool:
+
+```
+task(
+ agent="general-purpose",
+ task="Research the top 5 competitors of Acme Corp and summarize their pricing",
+ context="Focus on B2B SaaS pricing models"
+)
+```
+
+The runtime then:
+
+1. Looks up the subagent configuration from the registry, applying any `config.yaml` overrides.
+2. Creates a new agent invocation with the subagent's own prompt and tools.
+3. Runs the subagent to completion (or until timeout / max turns).
+4. Returns the subagent's final output to the Lead Agent as the tool result.
+
+## Configuration
+
+Subagent timeouts and max turns are controlled through the `subagents:` section in `config.yaml`:
+
+```yaml
+subagents:
+ # Default timeout in seconds for all subagents (default: 900 = 15 minutes)
+ timeout_seconds: 900
+
+ # Optional: override max turns for all subagents
+ # max_turns: 120
+
+ # Optional: per-agent overrides
+ agents:
+ general-purpose:
+ timeout_seconds: 1800 # 30 minutes for complex tasks
+ max_turns: 160
+ bash:
+ timeout_seconds: 300 # 5 minutes for quick commands
+ max_turns: 80
+```
+
+Per-agent overrides take priority over the global `timeout_seconds` and `max_turns` settings.
+
+## Concurrency limits
+
+The `SubagentLimitMiddleware` controls how many subagents the Lead Agent can invoke in parallel in a single turn. This is controlled through the per-request configuration:
+
+- `subagent_enabled`: whether subagent delegation is active for this session
+- `max_concurrent_subagents`: maximum parallel task calls in one turn (default: 3)
+
+If the agent tries to call more subagents than the limit allows, the middleware trims the excess calls.
+
+## ACP agents (external agents)
+
+In addition to the built-in subagents, DeerFlow supports delegating to external agents through the **Agent Connect Protocol (ACP)**. ACP allows DeerFlow to invoke agents running as separate processes (including third-party CLI tools wrapped with an ACP adapter).
+
+Configure ACP agents in `config.yaml`:
+
+```yaml
+acp_agents:
+ claude_code:
+ command: npx
+ args: ["-y", "@zed-industries/claude-agent-acp"]
+ description: Claude Code for implementation, refactoring, and debugging
+ model: null
+ # auto_approve_permissions: false
+ # env:
+ # ANTHROPIC_API_KEY: $ANTHROPIC_API_KEY
+
+ codex:
+ command: npx
+ args: ["-y", "@zed-industries/codex-acp"]
+ description: Codex CLI for repository tasks and code generation
+ model: null
+```
+
+The Lead Agent invokes ACP agents through the `invoke_acp_agent` built-in tool.
+
+
+ ACP agents run as child processes managed by DeerFlow. They communicate over
+ the ACP wire protocol. The standard CLI tools (like the plain `claude` or
+ `codex` commands) are not ACP-compatible by default — use the adapter packages
+ listed above or a compatible ACP wrapper.
+
+
+## Custom agents as subagents
+
+Custom agents created through the DeerFlow App UI can also be invoked as subagents using the `task` tool. When you specify `agent="my-custom-agent"`, the runtime loads that agent's configuration (skills, tool groups, model) and runs it as a subagent for the delegated task.
+
+
+
+
+
diff --git a/frontend/src/content/en/harness/tools.mdx b/frontend/src/content/en/harness/tools.mdx
index ad9493e402..f9cf9a2343 100644
--- a/frontend/src/content/en/harness/tools.mdx
+++ b/frontend/src/content/en/harness/tools.mdx
@@ -1,3 +1,245 @@
+---
+title: Tools
+description: "The Lead Agent is a tool-calling agent. Tools are how it interacts with the world: searching the web, reading and writing files, running commands, delegating tasks, and presenting outputs to the user."
+---
+
+import { Callout, Cards, Tabs } from "nextra/components";
+
# Tools
-TBD
+
+ Tools are the actions the Lead Agent can take. DeerFlow provides built-in
+ tools, community integrations, MCP tools, and skill tools — all controlled
+ through config.yaml.
+
+
+The Lead Agent is a tool-calling agent. Tools are how it interacts with the world: searching the web, reading and writing files, running commands, delegating tasks, and presenting outputs to the user.
+
+DeerFlow organizes tools into four categories:
+
+1. **Built-in tools** — core runtime capabilities always available to the agent
+2. **Community tools** — integrations with external search, fetch, and image services
+3. **MCP tools** — tools provided by external Model Context Protocol servers
+4. **Skill tools** — tools bundled with specific skill packs
+
+## Built-in tools
+
+Built-in tools are part of the harness and do not require configuration to be available.
+
+### task
+
+Delegates a subtask to a subagent. The Lead Agent uses this tool when a task is too broad for a single reasoning thread or when parallel work would be beneficial.
+
+```
+task(agent="general-purpose", task="...", context="...")
+```
+
+See the [Subagents](/docs/harness/subagents) page for how subagents are configured.
+
+---
+
+### present_files
+
+Presents output files to the user as artifacts. The agent calls this tool after producing a file (report, chart, code, etc.) to surface it in the conversation.
+
+Files at `/mnt/user-data/uploads/*` are copied into `/mnt/user-data/outputs/*` before being presented. The artifact paths are tracked in `ThreadState.artifacts`.
+
+---
+
+### view_image
+
+Reads an image file and injects its content into the model's context for visual analysis. Only available when the active model has `supports_vision: true`.
+
+---
+
+### clarification
+
+Asks the user a clarifying question before proceeding. This is triggered by the `ClarificationMiddleware` when the model decides it does not have enough information to act.
+
+---
+
+### setup_agent
+
+Dynamically configures the current agent session. Used during the bootstrap flow when setting up a new custom agent.
+
+---
+
+### invoke_acp_agent
+
+Invokes an external agent using the [Agent Connect Protocol (ACP)](https://agentconnectprotocol.org/). Requires `acp_agents:` configuration in `config.yaml`. See the [Subagents](/docs/harness/subagents) page for ACP configuration.
+
+---
+
+### tool_search
+
+Searches for tools by name or description and loads them into the agent's context on demand. Only active when `tool_search.enabled: true` in `config.yaml`. Useful when MCP or other tool sets expose many tools and you want to reduce context usage.
+
+## Sandbox file tools
+
+The following tools interact with the sandbox filesystem. They require a sandbox to be configured and active.
+
+| Tool | Description |
+| ------------- | --------------------------------------------------------------------------------- |
+| `ls` | List files in a directory |
+| `read_file` | Read file contents |
+| `glob` | Find files matching a pattern |
+| `grep` | Search file contents |
+| `write_file` | Write content to a file |
+| `str_replace` | Replace a string in a file |
+| `bash` | Execute a shell command (requires `allow_host_bash: true` or a container sandbox) |
+
+These are configured in `config.yaml` under `tools:`:
+
+```yaml
+tools:
+ - use: deerflow.sandbox.tools:ls_tool
+ - use: deerflow.sandbox.tools:read_file_tool
+ - use: deerflow.sandbox.tools:glob_tool
+ - use: deerflow.sandbox.tools:grep_tool
+ - use: deerflow.sandbox.tools:write_file_tool
+ - use: deerflow.sandbox.tools:str_replace_tool
+ - use: deerflow.sandbox.tools:bash_tool # requires host bash or container sandbox
+```
+
+## Community tools
+
+Community tools connect the agent to external services. They are configured in `config.yaml` under `tools:` using the `use:` field to specify the implementation.
+
+### Web search
+
+
+
+```yaml
+tools:
+ - use: deerflow.community.ddg_search.tools:web_search_tool
+```
+No API key required. Default configuration. Suitable for development and general use.
+
+
+```yaml
+tools:
+ - use: deerflow.community.tavily.tools:web_search_tool
+ api_key: $TAVILY_API_KEY
+```
+High-quality search with structured results. Requires a [Tavily](https://tavily.com) API key.
+
+Install: `cd backend && uv add 'deerflow-harness[tavily]'`
+
+
+
+```yaml
+tools:
+ - use: deerflow.community.exa.tools:web_search_tool
+ api_key: $EXA_API_KEY
+```
+Semantic search with neural retrieval. Requires an [Exa](https://exa.ai) API key.
+
+Install: `cd backend && uv add 'deerflow-harness[exa]'`
+
+
+
+```yaml
+tools:
+ - use: deerflow.community.infoquest.tools:web_search_tool
+ api_key: $INFOQUEST_API_KEY
+```
+InfoQuest search integration.
+
+
+```yaml
+tools:
+ - use: deerflow.community.firecrawl.tools:web_search_tool
+ api_key: $FIRECRAWL_API_KEY
+```
+Firecrawl-powered search and crawl. Requires a [Firecrawl](https://firecrawl.dev) API key.
+
+Install: `cd backend && uv add 'deerflow-harness[firecrawl]'`
+
+
+
+
+### Web fetch (page content extraction)
+
+
+
+```yaml
+tools:
+ - use: deerflow.community.jina_ai.tools:web_fetch_tool
+ api_key: $JINA_API_KEY # optional; anonymous usage has rate limits
+```
+Converts web pages to clean Markdown. Works without an API key at reduced rate
+limits.
+
+
+```yaml
+tools:
+ - use: deerflow.community.exa.tools:web_fetch_tool
+ api_key: $EXA_API_KEY
+```
+
+
+```yaml
+tools:
+ - use: deerflow.community.infoquest.tools:web_fetch_tool
+ api_key: $INFOQUEST_API_KEY
+```
+
+
+```yaml
+tools:
+ - use: deerflow.community.firecrawl.tools:web_fetch_tool
+ api_key: $FIRECRAWL_API_KEY
+```
+
+
+
+### Image search
+
+```yaml
+tools:
+ - use: deerflow.community.image_search.tools:image_search_tool
+ # Or use InfoQuest:
+ # - use: deerflow.community.infoquest.tools:image_search_tool
+ # api_key: $INFOQUEST_API_KEY
+```
+
+## Tool groups
+
+Tool groups let you organize tools into named sets and restrict which groups a custom agent can access.
+
+```yaml
+tool_groups:
+ - name: research
+ tools:
+ - web_search
+ - web_fetch
+ - image_search
+ - name: coding
+ tools:
+ - bash
+ - read_file
+ - write_file
+ - str_replace
+ - glob
+ - grep
+```
+
+Custom agents can then reference a group by name in their configuration, restricting their tool access to only the relevant set.
+
+## Tool search (deferred loading)
+
+When you have many tools (especially from multiple MCP servers), loading all of them upfront increases context usage and can confuse the model. The tool search feature addresses this:
+
+```yaml
+tool_search:
+ enabled: true
+```
+
+When enabled, tools are not listed in the model's context directly. Instead, they are discoverable at runtime via the `tool_search` built-in tool. The agent searches by name or description and the matching tools are loaded into context on demand.
+
+This is particularly useful when MCP servers expose dozens of tools.
+
+
+
+
+
diff --git a/frontend/src/content/en/introduction/core-concepts.mdx b/frontend/src/content/en/introduction/core-concepts.mdx
index dfed365cb4..f7203ae1e2 100644
--- a/frontend/src/content/en/introduction/core-concepts.mdx
+++ b/frontend/src/content/en/introduction/core-concepts.mdx
@@ -1,3 +1,8 @@
+---
+title: Core Concepts
+description: Before you go deeper into DeerFlow, it helps to anchor on a few concepts that appear throughout the system. These concepts explain what DeerFlow is optimizing for and why its architecture looks the way it does.
+---
+
import { Callout, Cards } from "nextra/components";
# Core Concepts
diff --git a/frontend/src/content/en/introduction/harness-vs-app.mdx b/frontend/src/content/en/introduction/harness-vs-app.mdx
index e0bbb6d55c..9e6b8ece2b 100644
--- a/frontend/src/content/en/introduction/harness-vs-app.mdx
+++ b/frontend/src/content/en/introduction/harness-vs-app.mdx
@@ -1,3 +1,8 @@
+---
+title: Harness vs App
+description: "DeerFlow has two layers that are closely related but serve different purposes."
+---
+
import { Callout, Cards } from "nextra/components";
# Harness vs App
diff --git a/frontend/src/content/en/introduction/why-deerflow.mdx b/frontend/src/content/en/introduction/why-deerflow.mdx
index f7fad96287..66ca91f318 100644
--- a/frontend/src/content/en/introduction/why-deerflow.mdx
+++ b/frontend/src/content/en/introduction/why-deerflow.mdx
@@ -1,3 +1,8 @@
+---
+title: Why DeerFlow
+description: DeerFlow exists because modern agent systems need more than a chat loop. A useful agent must plan over long horizons, break work into sub-tasks, use tools, manipulate files, run code safely, and preserve enough context to stay coherent across a complex task. DeerFlow was built to provide that runtime foundation.
+---
+
import { Callout, Cards } from "nextra/components";
# Why DeerFlow
diff --git a/frontend/src/content/en/reference/_meta.ts b/frontend/src/content/en/reference/_meta.ts
index cf53ad9a82..835fd6ce00 100644
--- a/frontend/src/content/en/reference/_meta.ts
+++ b/frontend/src/content/en/reference/_meta.ts
@@ -1,20 +1,8 @@
import type { MetaRecord } from "nextra";
const meta: MetaRecord = {
- "concepts-glossary": {
- title: "Concepts Glossary",
- },
- "configuration-reference": {
- title: "Configuration Reference",
- },
- "api-gateway-reference": {
- title: "API / Gateway Reference",
- },
- "runtime-flags-and-modes": {
- title: "Runtime Flags and Modes",
- },
- "source-map": {
- title: "Source Map",
+ "model-providers": {
+ title: "Model providers",
},
};
diff --git a/frontend/src/content/en/reference/api-gateway-reference.mdx b/frontend/src/content/en/reference/api-gateway-reference.mdx
deleted file mode 100644
index 2a5922d2b9..0000000000
--- a/frontend/src/content/en/reference/api-gateway-reference.mdx
+++ /dev/null
@@ -1,3 +0,0 @@
-# API / Gateway Reference
-
-TBD
diff --git a/frontend/src/content/en/reference/concepts-glossary.mdx b/frontend/src/content/en/reference/concepts-glossary.mdx
deleted file mode 100644
index 32f000a7e7..0000000000
--- a/frontend/src/content/en/reference/concepts-glossary.mdx
+++ /dev/null
@@ -1,3 +0,0 @@
-# Concepts Glossary
-
-TBD
diff --git a/frontend/src/content/en/reference/configuration-reference.mdx b/frontend/src/content/en/reference/configuration-reference.mdx
deleted file mode 100644
index cbc914ddaf..0000000000
--- a/frontend/src/content/en/reference/configuration-reference.mdx
+++ /dev/null
@@ -1,3 +0,0 @@
-# Configuration Reference
-
-TBD
diff --git a/frontend/src/content/en/reference/model-providers/_meta.ts b/frontend/src/content/en/reference/model-providers/_meta.ts
new file mode 100644
index 0000000000..81fd1b56f6
--- /dev/null
+++ b/frontend/src/content/en/reference/model-providers/_meta.ts
@@ -0,0 +1,9 @@
+import type { MetaRecord } from "nextra";
+
+const meta: MetaRecord = {
+ ark: {
+ title: "火山方舟",
+ },
+};
+
+export default meta;
diff --git a/frontend/src/content/en/reference/model-providers/ark.mdx b/frontend/src/content/en/reference/model-providers/ark.mdx
new file mode 100644
index 0000000000..5956d691b3
--- /dev/null
+++ b/frontend/src/content/en/reference/model-providers/ark.mdx
@@ -0,0 +1,8 @@
+---
+title: Volcano Ark
+description: Integration guide for the Volcano Ark model provider.
+---
+
+# Volcano Ark
+
+## Coding Plan
diff --git a/frontend/src/content/en/reference/model-providers/index.mdx b/frontend/src/content/en/reference/model-providers/index.mdx
new file mode 100644
index 0000000000..f9b506a6dc
--- /dev/null
+++ b/frontend/src/content/en/reference/model-providers/index.mdx
@@ -0,0 +1,7 @@
+---
+title: Model providers
+description: Integration references for supported model provider services.
+asIndexPage: true
+---
+
+# Model providers
diff --git a/frontend/src/content/en/reference/runtime-flags-and-modes.mdx b/frontend/src/content/en/reference/runtime-flags-and-modes.mdx
deleted file mode 100644
index 76ce6f9abd..0000000000
--- a/frontend/src/content/en/reference/runtime-flags-and-modes.mdx
+++ /dev/null
@@ -1,3 +0,0 @@
-# Runtime Flags and Modes
-
-TBD
diff --git a/frontend/src/content/en/reference/source-map.mdx b/frontend/src/content/en/reference/source-map.mdx
deleted file mode 100644
index f5a4823da4..0000000000
--- a/frontend/src/content/en/reference/source-map.mdx
+++ /dev/null
@@ -1,3 +0,0 @@
-# Source Map
-
-TBD
diff --git a/frontend/src/content/en/tutorials/create-your-first-harness.mdx b/frontend/src/content/en/tutorials/create-your-first-harness.mdx
index b692e09e06..3e94f984ca 100644
--- a/frontend/src/content/en/tutorials/create-your-first-harness.mdx
+++ b/frontend/src/content/en/tutorials/create-your-first-harness.mdx
@@ -1,3 +1,104 @@
+---
+title: Create Your First Harness
+description: This tutorial shows you how to use the DeerFlow Harness programmatically — importing and using DeerFlow directly in your Python code rather than through the web interface.
+---
+
+import { Callout, Steps } from "nextra/components";
+
# Create Your First Harness
-TBD
+This tutorial shows you how to use the DeerFlow Harness programmatically — importing and using DeerFlow directly in your Python code rather than through the web interface.
+
+## Prerequisites
+
+- Python 3.12+
+- `uv` installed
+- DeerFlow repository cloned
+
+## Install
+
+```bash
+cd deer-flow/backend
+uv sync
+```
+
+## Create configuration
+
+Create a minimal `config.yaml`:
+
+```yaml
+config_version: 6
+
+models:
+ - name: gpt-4o
+ use: langchain_openai:ChatOpenAI
+ model: gpt-4o
+ api_key: $OPENAI_API_KEY
+
+sandbox:
+ use: deerflow.sandbox.local:LocalSandboxProvider
+
+tools:
+ - use: deerflow.community.ddg_search.tools:web_search_tool
+ - use: deerflow.sandbox.tools:read_file_tool
+ - use: deerflow.sandbox.tools:write_file_tool
+```
+
+## Write the code
+
+
+
+### Create a Python file
+
+Create `my_agent.py` in the `backend/` directory:
+
+```python
+import asyncio
+import os
+from deerflow.client import DeerFlowClient
+from deerflow.config import load_config
+
+os.environ["OPENAI_API_KEY"] = "sk-..."
+
+# Load config.yaml
+load_config()
+
+client = DeerFlowClient()
+
+async def main():
+ async for event in client.astream(
+ thread_id="my-first-thread",
+ message="Write a Python fibonacci function with a docstring",
+ config={
+ "configurable": {
+ "model_name": "gpt-4o",
+ }
+ },
+ ):
+ print(event)
+
+asyncio.run(main())
+```
+
+### Run it
+
+```bash
+cd backend
+uv run python my_agent.py
+```
+
+
+
+## What the events look like
+
+The stream yields events like:
+
+```python
+{"type": "messages", "data": {"content": "def fibonacci..."}}
+{"type": "thread_state", "data": {"title": "Python Fibonacci Function"}}
+```
+
+## Next steps
+
+- [Use Tools and Skills](/docs/tutorials/use-tools-and-skills)
+- [Harness Quick Start](/docs/harness/quick-start)
diff --git a/frontend/src/content/en/tutorials/deploy-your-own-deerflow.mdx b/frontend/src/content/en/tutorials/deploy-your-own-deerflow.mdx
index 4ecfbaec17..bd62bbb518 100644
--- a/frontend/src/content/en/tutorials/deploy-your-own-deerflow.mdx
+++ b/frontend/src/content/en/tutorials/deploy-your-own-deerflow.mdx
@@ -1,3 +1,79 @@
+---
+title: Deploy Your Own DeerFlow
+description: This tutorial guides you through deploying DeerFlow to a production environment using Docker Compose for multi-user access.
+---
+
+import { Callout, Steps } from "nextra/components";
+
# Deploy Your Own DeerFlow
-TBD
+This tutorial guides you through deploying DeerFlow to a production environment using Docker Compose for multi-user access.
+
+## Prerequisites
+
+- Docker and Docker Compose installed
+- A server or VM (Linux recommended)
+- LLM API key
+
+## Steps
+
+
+
+### Clone the repository
+
+```bash
+git clone https://github.com/bytedance/deer-flow.git
+cd deer-flow
+```
+
+### Create the configuration file
+
+```bash
+cp config.example.yaml config.yaml
+```
+
+Edit `config.yaml` to add your model configuration.
+
+### Create the environment variables file
+
+```bash
+cat > .env << EOF
+OPENAI_API_KEY=sk-your-key-here
+DEER_FLOW_ROOT=$(pwd)
+BETTER_AUTH_SECRET=$(openssl rand -base64 32)
+BETTER_AUTH_URL=https://your-domain.com
+EOF
+```
+
+### Start the services
+
+```bash
+docker compose -f docker/docker-compose-dev.yaml up -d
+```
+
+### Verify the deployment
+
+```bash
+# Check all services are healthy
+curl http://localhost:2026/api/models
+
+# Follow logs
+docker compose -f docker/docker-compose-dev.yaml logs -f
+```
+
+Open `http://your-server:2026` in your browser.
+
+
+
+## Production checklist
+
+- Configure HTTPS/TLS for nginx
+- Set `BETTER_AUTH_SECRET` to a strong random string (minimum 32 characters)
+- Configure firewall rules to allow only necessary ports
+- Back up `backend/.deer-flow/` directory regularly
+- Consider using a container-based sandbox (`AioSandboxProvider`) for multi-user isolation
+
+## Next steps
+
+- [Full Deployment Guide](/docs/application/deployment-guide)
+- [Operations and Troubleshooting](/docs/application/operations-and-troubleshooting)
diff --git a/frontend/src/content/en/tutorials/first-conversation.mdx b/frontend/src/content/en/tutorials/first-conversation.mdx
index 71ff683cfa..64776fd424 100644
--- a/frontend/src/content/en/tutorials/first-conversation.mdx
+++ b/frontend/src/content/en/tutorials/first-conversation.mdx
@@ -1,3 +1,67 @@
+---
+title: First Conversation
+description: This tutorial walks you through your first complete agent conversation in DeerFlow — from launching the app to getting meaningful work done with the agent.
+---
+
+import { Callout, Steps } from "nextra/components";
+
# First Conversation
-TBD
+This tutorial walks you through your first complete agent conversation in DeerFlow — from launching the app to getting meaningful work done with the agent.
+
+## Prerequisites
+
+- DeerFlow app is running (see [Quick Start](/docs/application/quick-start))
+- At least one model is configured in `config.yaml`
+
+## Steps
+
+
+
+### Open the workspace
+
+Open [http://localhost:2026](http://localhost:2026) in your browser. You will see the conversation workspace.
+
+### Send your first message
+
+Type a question in the input box, for example:
+
+```
+Research the top 3 most popular open-source LLM frameworks in 2024 and compare their strengths and weaknesses.
+```
+
+Press Enter to send.
+
+### Watch the agent work
+
+You will see the agent start working:
+
+- Expand the **thinking steps** to see which tools it is calling
+- Watch search results stream in
+- Wait for the final report to be generated
+
+### Interact with the result
+
+Once the report is generated, you can:
+
+- Ask for more detail on a specific section
+- Ask to export the report as a file (the agent will use the `present_files` tool)
+- Ask to create a chart based on the research findings
+
+
+
+## What just happened
+
+The agent used the DeerFlow Harness to:
+
+1. Receive your message and add it to the thread state
+2. Run the middleware chain (memory injection, title generation)
+3. Call the LLM, which decided to search the web
+4. Execute web search tool calls
+5. Synthesize results into a structured response
+6. Update the thread state with any artifacts produced
+
+## Next steps
+
+- [Use Tools and Skills](/docs/tutorials/use-tools-and-skills)
+- [Workspace Usage](/docs/application/workspace-usage)
diff --git a/frontend/src/content/en/tutorials/use-tools-and-skills.mdx b/frontend/src/content/en/tutorials/use-tools-and-skills.mdx
index 0d1af3a817..2a12051f45 100644
--- a/frontend/src/content/en/tutorials/use-tools-and-skills.mdx
+++ b/frontend/src/content/en/tutorials/use-tools-and-skills.mdx
@@ -1,3 +1,65 @@
+---
+title: Use Tools and Skills
+description: This tutorial shows you how to configure and use tools and skills in DeerFlow to give the agent access to web search, file operations, and domain-specific capabilities.
+---
+
+import { Callout } from "nextra/components";
+
# Use Tools and Skills
-TBD
+This tutorial shows you how to configure and use tools and skills in DeerFlow to give the agent access to web search, file operations, and domain-specific capabilities.
+
+## Configuring tools
+
+Add tools to `config.yaml`:
+
+```yaml
+tools:
+ # Web search
+ - use: deerflow.community.ddg_search.tools:web_search_tool
+
+ # Web content fetching
+ - use: deerflow.community.jina_ai.tools:web_fetch_tool
+
+ # Sandbox file operations
+ - use: deerflow.sandbox.tools:ls_tool
+ - use: deerflow.sandbox.tools:read_file_tool
+ - use: deerflow.sandbox.tools:write_file_tool
+ - use: deerflow.sandbox.tools:bash_tool
+```
+
+## Enabling skills
+
+Enable skills through the DeerFlow app's extensions panel, or edit `extensions_config.json` directly.
+
+**Via the app UI:**
+
+1. Open the DeerFlow app
+2. Click the Extensions/Skills icon in the sidebar
+3. Find `deep-research` and toggle it on
+
+## Using a skill for research
+
+With the `deep-research` skill enabled, select it in the conversation input, then send a research request:
+
+```
+Do a deep research on the latest advances in quantum computing, focusing on practical applications.
+```
+
+The agent will run a multi-step research workflow including web search, information synthesis, and report generation.
+
+## Using the data analysis skill
+
+Enable `data-analysis`, then upload a CSV or data file and ask the agent to analyze it:
+
+```
+Analyze this CSV file and identify the top trends.
+```
+
+The agent will use the sandbox tools to read the file, run analysis, and produce charts.
+
+## Next steps
+
+- [Work with Memory](/docs/tutorials/work-with-memory)
+- [Tools Reference](/docs/harness/tools)
+- [Skills Reference](/docs/harness/skills)
diff --git a/frontend/src/content/en/tutorials/work-with-memory.mdx b/frontend/src/content/en/tutorials/work-with-memory.mdx
index f32d0f9ee9..c7aa047dbc 100644
--- a/frontend/src/content/en/tutorials/work-with-memory.mdx
+++ b/frontend/src/content/en/tutorials/work-with-memory.mdx
@@ -1,3 +1,71 @@
+---
+title: Work with Memory
+description: This tutorial shows you how to enable and use DeerFlow's memory system so the agent remembers important information about you across multiple sessions.
+---
+
+import { Callout } from "nextra/components";
+
# Work with Memory
-TBD
+This tutorial shows you how to enable and use DeerFlow's memory system so the agent remembers important information about you across multiple sessions.
+
+## Enable memory
+
+In `config.yaml`:
+
+```yaml
+memory:
+ enabled: true
+ injection_enabled: true
+ max_injection_tokens: 2000
+ debounce_seconds: 30
+```
+
+## How memory works
+
+Memory works automatically through `MemoryMiddleware`:
+
+1. **First conversation**: tell the agent about your preferences, project, or background.
+2. **Automatic learning**: the agent extracts and saves important facts in the background.
+3. **Future conversations**: memory facts are automatically injected into the system prompt — the agent does not need you to repeat context.
+
+## Example
+
+**First conversation:**
+
+```
+I am a Python backend developer primarily using FastAPI and PostgreSQL.
+My team follows PEP 8 and prefers type annotations everywhere.
+Please remember this for future code suggestions.
+```
+
+**Later conversation** (no need to repeat background):
+
+```
+Help me write a user authentication module
+```
+
+The agent will automatically produce FastAPI-style code with type annotations.
+
+## Inspect memory
+
+Memory is stored in `backend/.deer-flow/memory.json`:
+
+```bash
+cat backend/.deer-flow/memory.json
+```
+
+## Per-agent memory
+
+When a custom agent is active, it maintains its own memory file at:
+
+```
+backend/.deer-flow/agents/{agent_name}/memory.json
+```
+
+This keeps each agent's learned knowledge separate.
+
+## Next steps
+
+- [Deploy Your Own DeerFlow](/docs/tutorials/deploy-your-own-deerflow)
+- [Memory System Reference](/docs/harness/memory)
diff --git a/frontend/src/content/zh/_meta.ts b/frontend/src/content/zh/_meta.ts
index 8bd4c55752..9540c441e6 100644
--- a/frontend/src/content/zh/_meta.ts
+++ b/frontend/src/content/zh/_meta.ts
@@ -4,12 +4,36 @@ const meta: MetaRecord = {
index: {
title: "概览",
},
+ introduction: {
+ title: "简介",
+ },
+ harness: {
+ title: "DeerFlow Harness",
+ },
+ application: {
+ title: "DeerFlow 应用",
+ },
+ tutorials: {
+ title: "教程",
+ },
+ reference: {
+ title: "参考",
+ },
workspace: {
type: "page",
},
blog: {
type: "page",
},
+ posts: {
+ type: "page",
+ },
+ login: {
+ type: "page",
+ },
+ setup: {
+ type: "page",
+ },
};
export default meta;
diff --git a/frontend/src/content/zh/application/_meta.ts b/frontend/src/content/zh/application/_meta.ts
new file mode 100644
index 0000000000..8cbba95d53
--- /dev/null
+++ b/frontend/src/content/zh/application/_meta.ts
@@ -0,0 +1,27 @@
+import type { MetaRecord } from "nextra";
+
+const meta: MetaRecord = {
+ index: {
+ title: "概览",
+ },
+ "quick-start": {
+ title: "快速上手",
+ },
+ "deployment-guide": {
+ title: "部署指南",
+ },
+ configuration: {
+ title: "配置",
+ },
+ "workspace-usage": {
+ title: "工作区使用",
+ },
+ "agents-and-threads": {
+ title: "Agent 与线程",
+ },
+ "operations-and-troubleshooting": {
+ title: "运维与排障",
+ },
+};
+
+export default meta;
diff --git a/frontend/src/content/zh/application/agents-and-threads.mdx b/frontend/src/content/zh/application/agents-and-threads.mdx
new file mode 100644
index 0000000000..6ad982fed2
--- /dev/null
+++ b/frontend/src/content/zh/application/agents-and-threads.mdx
@@ -0,0 +1,123 @@
+---
+title: Agent 与线程
+description: 了解 DeerFlow 中 Agent 与线程的关系,以及如何管理自定义 Agent 和对话线程。
+---
+
+import { Callout, Cards, Steps } from "nextra/components";
+
+# Agent 与线程
+
+
+ Agent
+ 是配置单元——它们定义了一组能力。线程是对话实例,带有持久化状态和历史记录。
+
+
+## 自定义 Agent
+
+DeerFlow 允许你创建多个具有不同专业领域的自定义 Agent。每个 Agent 使用 DeerFlow Harness 相同的 Lead Agent 运行时,但具有不同的:
+
+- 模型(例如为一个 Agent 使用 GPT-4o,为另一个使用 Claude)
+- 系统提示和指令
+- 技能(例如专注于数据分析的 Agent 只加载数据分析技能)
+- 工具访问(通过工具组)
+
+### 创建自定义 Agent
+
+
+
+#### 通过界面
+
+1. 打开 DeerFlow 应用。
+2. 点击侧边栏中的 **Agents**(Agent 管理)。
+3. 点击 **New Agent**(新建 Agent)。
+4. 填写名称、描述和配置。
+5. 保存——Agent 立即可用,无需重启。
+
+#### 通过 API
+
+```bash
+curl -X POST http://localhost:8001/api/agents \
+ -H "Content-Type: application/json" \
+ -d '{
+ "display_name": "数据分析师",
+ "description": "专业的数据分析和可视化",
+ "skills": ["data-analysis", "chart-visualization"]
+ }'
+```
+
+
+
+### Agent 名称和 Slug
+
+创建 Agent 时,`display_name` 对用户显示,系统内部使用自动派生的 ASCII `slug`(`name` 字段)来标识 Agent。
+
+- `display_name`:对用户显示的任意字符串(例如 "数据分析师")
+- `name`(slug):用于 API 和文件路径的 ASCII 标识符(例如 `data-analyst`)
+
+如果派生的 slug 与现有 Agent 冲突,`/api/agents/check` 端点会建议一个唯一的替代名称。
+
+### Agent 存储
+
+自定义 Agent 配置存储在 `backend/agents/{name}/config.yaml` 中。你可以直接编辑这些文件——更改在下次 Agent 调用时自动加载。
+
+## 线程生命周期
+
+线程是对话及其所有相关状态的完整封装:消息历史、产出物、待办列表和检查点数据。
+
+### 创建
+
+线程在你发送第一条消息时创建。线程 ID 是自动生成的 UUID(由前端生成),用于标识所有后续请求中的对话。
+
+### 执行
+
+每次你发送消息,LangGraph 从最新检查点恢复线程状态,运行 Lead Agent,并更新状态。流式事件在 Agent 工作时发送到浏览器。
+
+### 检查点和持久化
+
+DeerFlow 在每次 Agent 轮次后自动保存线程状态(如果配置了检查点器)。这允许:
+
+- 在服务器重启后恢复线程。
+- 在长时间间隔后继续对话。
+- 在出现问题时重放或从特定时间点恢复。
+
+配置检查点器:
+
+```yaml
+checkpointer:
+ type: sqlite
+ connection_string: .deer-flow/checkpoints.db
+```
+
+对于生产高负载环境使用 Redis:
+
+```yaml
+checkpointer:
+ type: redis
+ connection_string: redis://localhost:6379/0
+```
+
+### 线程数据目录
+
+每个线程在 `.deer-flow/threads/{thread_id}/` 下有一个专用目录:
+
+```
+.deer-flow/threads/{thread_id}/
+ user-data/
+ uploads/ ← 用户上传的文件
+ outputs/ ← Agent 生成的产出物
+ workspace/ ← 会话工作文件
+```
+
+删除线程会移除其检查点数据,但用户数据目录会单独管理(取决于删除配置)。
+
+### 恢复线程
+
+线程以其持久化状态自动恢复。从侧边栏选择任何过往线程即可从停止处继续——Agent 会记住所有消息和产出物。
+
+
+
+
+
diff --git a/frontend/src/content/zh/application/configuration.mdx b/frontend/src/content/zh/application/configuration.mdx
new file mode 100644
index 0000000000..639eeaec50
--- /dev/null
+++ b/frontend/src/content/zh/application/configuration.mdx
@@ -0,0 +1,223 @@
+---
+title: 配置
+description: 本页面涵盖 DeerFlow 应用的所有配置层——`config.yaml`、前端环境变量、`extensions_config.json` 和运行时环境变量。
+---
+
+import { Callout, Cards, Tabs } from "nextra/components";
+
+# 配置
+
+本页面涵盖 DeerFlow 应用的所有配置层——`config.yaml`、前端环境变量、`extensions_config.json` 和运行时环境变量。
+
+## config.yaml
+
+`config.yaml` 是 DeerFlow 的主要配置文件。所有 Agent 行为、模型选择、工具加载和运行时功能都由它控制。
+
+参见[配置](/docs/harness/configuration)参考页面了解文件格式和每个章节的详细说明。
+
+### 模型提供商
+
+
+
+```yaml
+models:
+ - name: gpt-4o
+ use: langchain_openai:ChatOpenAI
+ model: gpt-4o
+ api_key: $OPENAI_API_KEY
+ request_timeout: 600.0
+ max_retries: 2
+ supports_vision: true
+ thinking_enabled: false
+```
+
+
+```yaml
+models:
+ - name: claude-sonnet
+ use: langchain_anthropic:ChatAnthropic
+ model: claude-sonnet-4-5
+ api_key: $ANTHROPIC_API_KEY
+ max_tokens: 16000
+ supports_vision: true
+ thinking_enabled: false
+```
+
+启用扩展思考:
+
+```yaml
+- name: claude-extended-thinking
+ use: langchain_anthropic:ChatAnthropic
+ model: claude-sonnet-4-5
+ api_key: $ANTHROPIC_API_KEY
+ max_tokens: 16000
+ thinking_enabled: true
+ extra_body:
+ thinking:
+ type: enabled
+ budget_tokens: 10000
+```
+
+
+
+```yaml
+models:
+ - name: gemini
+ use: langchain_google_genai:ChatGoogleGenerativeAI
+ model: gemini-2.5-pro-preview-03-25
+ api_key: $GEMINI_API_KEY
+ request_timeout: 600.0
+ max_retries: 2
+ supports_vision: true
+ thinking_enabled: false
+```
+
+
+```yaml
+models:
+ - name: deepseek
+ use: langchain_openai:ChatOpenAI
+ model: deepseek-reasoner
+ api_key: $DEEPSEEK_API_KEY
+ base_url: https://api.deepseek.com
+ request_timeout: 600.0
+ max_retries: 2
+ supports_vision: false
+ thinking_enabled: true
+```
+
+
+```yaml
+models:
+ - name: ollama-llama
+ use: langchain_ollama:ChatOllama
+ model: llama3.3
+ base_url: http://localhost:11434
+ request_timeout: 600.0
+ max_retries: 2
+ supports_vision: false
+ thinking_enabled: false
+```
+
+确保 Ollama 已运行并已拉取所需模型:
+
+```bash
+ollama pull llama3.3
+```
+
+
+
+
+### 沙箱
+
+```yaml
+# 本地开发(默认)
+sandbox:
+ use: deerflow.sandbox.local:LocalSandboxProvider
+ allow_host_bash: false
+
+# 基于容器(推荐用于多用户)
+sandbox:
+ use: deerflow.community.aio_sandbox:AioSandboxProvider
+ replicas: 3
+ idle_timeout: 600
+```
+
+### 工具
+
+参见[工具](/docs/harness/tools)页面了解完整的工具配置参考。快速参考:
+
+```yaml
+tools:
+ - use: deerflow.community.ddg_search.tools:web_search_tool
+ - use: deerflow.community.jina_ai.tools:web_fetch_tool
+ - use: deerflow.sandbox.tools:ls_tool
+ - use: deerflow.sandbox.tools:read_file_tool
+ - use: deerflow.sandbox.tools:write_file_tool
+ - use: deerflow.sandbox.tools:bash_tool
+```
+
+### 技能
+
+```yaml
+skills:
+ container_path: /mnt/skills
+ # path: /custom/path/to/skills # 可选;默认为仓库 skills/ 目录
+```
+
+技能可用性在 `extensions_config.json` 中管理(参见下方)。
+
+### 检查点(线程持久化)
+
+```yaml
+checkpointer:
+ type: sqlite
+ connection_string: .deer-flow/checkpoints.db
+
+# 或使用 Redis(高负载生产环境):
+# checkpointer:
+# type: redis
+# connection_string: redis://localhost:6379/0
+```
+
+## 前端环境变量
+
+前端通过 `.env.local`(本地开发)或 Docker Compose 环境中的环境变量配置。
+
+| 变量 | 必需 | 描述 |
+| --------------------- | ---------- | -------------------------------------------------- |
+| `BETTER_AUTH_SECRET` | 是(生产) | 会话管理的密钥(最少 32 个字符) |
+| `BETTER_AUTH_URL` | 推荐 | 你的应用公开 URL(例如 `https://your-domain.com`) |
+| `SKIP_ENV_VALIDATION` | 否 | 设为 `1` 跳过构建时环境变量验证 |
+
+本地开发:
+
+```bash
+# frontend/.env.local
+BETTER_AUTH_SECRET=local-dev-secret-at-least-32-chars
+```
+
+
+ 不要在生产中使用 SKIP_ENV_VALIDATION=1。为{" "}
+ BETTER_AUTH_SECRET 设置一个真实值。
+
+
+## extensions_config.json
+
+`extensions_config.json` 控制 MCP 服务器和技能的运行时启用状态,独立于 `config.yaml`。
+
+默认位置:项目根目录(与 `config.yaml` 同一目录)。
+
+### MCP 服务器
+
+```json
+{
+ "mcpServers": {
+ "filesystem": {
+ "command": "npx",
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"],
+ "enabled": true
+ }
+ }
+}
+```
+
+### 技能启用状态
+
+技能启用状态会反映在 `extensions_config.json` 中。你可以直接编辑它,或通过 DeerFlow 应用界面进行管理。
+
+## 运行时环境变量
+
+这些变量在 DeerFlow 进程中设置(通过 `.env`、Docker 环境或 shell):
+
+| 变量 | 默认值 | 描述 |
+| ----------------------- | ---------------- | ------------------------------------------------ |
+| `DEER_FLOW_CONFIG_PATH` | 自动发现 | `config.yaml` 的绝对路径 |
+| `LOG_LEVEL` | `info` | 日志详细程度(`debug`/`info`/`warning`/`error`) |
+| `DEER_FLOW_ROOT` | 仓库根目录 | 用于 Docker 中的技能和线程挂载 |
+| `LANGGRAPH_UPSTREAM` | `langgraph:2024` | nginx 代理的 LangGraph 地址 |
+
+
+
+
+
diff --git a/frontend/src/content/zh/application/deployment-guide.mdx b/frontend/src/content/zh/application/deployment-guide.mdx
new file mode 100644
index 0000000000..59eceece20
--- /dev/null
+++ b/frontend/src/content/zh/application/deployment-guide.mdx
@@ -0,0 +1,212 @@
+---
+title: 部署指南
+description: 本指南涵盖 DeerFlow 应用所有支持的部署方式:本地开发、Docker Compose 以及使用 Kubernetes 管理沙箱的生产环境。
+---
+
+import { Callout, Cards, Steps, Tabs } from "nextra/components";
+
+# 部署指南
+
+本指南涵盖 DeerFlow 应用所有支持的部署方式:本地开发、Docker Compose 以及使用 Kubernetes 管理沙箱的生产环境。
+
+## 本地开发部署
+
+本地工作流是运行 DeerFlow 最快的方式,所有服务作为原生进程在你的机器上运行。
+
+
+
+```bash
+make dev
+```
+
+启动的服务:
+
+| 服务 | 端口 | 描述 |
+| ----------- | ---- | ----------------------- |
+| LangGraph | 2024 | DeerFlow Harness 运行时 |
+| Gateway API | 8001 | FastAPI 后端 |
+| 前端 | 3000 | Next.js 界面 |
+| nginx | 2026 | 统一反向代理 |
+
+访问地址:**http://localhost:2026**
+
+
+
+```bash
+make stop
+```
+
+停止所有四个服务。即使某个服务没有运行也可以安全执行。
+
+
+
+```
+logs/langgraph.log # Agent 运行时日志
+logs/gateway.log # API Gateway 日志
+logs/frontend.log # Next.js 开发服务器日志
+logs/nginx.log # nginx 访问/错误日志
+```
+
+实时追踪日志:
+
+```bash
+tail -f logs/langgraph.log
+```
+
+
+
+
+## Docker Compose 部署
+
+Docker Compose 在容器中运行所有服务。适用于更接近生产的本地设置或团队环境。
+
+### 前置条件
+
+- Docker(macOS 上的 Docker Desktop 或 OrbStack)
+- 在仓库根目录中已配置的 `config.yaml`
+
+### 开发 Compose
+
+```bash
+# 设置 deer-flow 仓库根目录的绝对路径
+export DEER_FLOW_ROOT=/path/to/deer-flow
+
+docker compose -f docker/docker-compose-dev.yaml up --build
+```
+
+访问地址:**http://localhost:2026**
+
+### 环境变量
+
+在仓库根目录创建 `.env` 文件用于存放密钥和运行时配置:
+
+```bash
+# .env
+OPENAI_API_KEY=sk-...
+DEER_FLOW_ROOT=/absolute/path/to/deer-flow
+BETTER_AUTH_SECRET=your-secret-here-min-32-chars
+```
+
+`docker-compose*.yaml` 文件包含 `env_file: ../.env` 指令,会自动加载该文件。
+
+
+ 在部署前始终将 BETTER_AUTH_SECRET{" "}
+ 设置为强随机字符串。没有它,前端构建会使用一个公开已知的默认值。
+
+
+### 数据持久化
+
+线程数据存储在 `backend/.deer-flow/threads/`。在 Docker 部署中,此目录被绑定挂载到 langgraph 容器中。
+
+为避免容器重建时数据丢失:
+
+1. 将 `DEER_FLOW_ROOT` 设置为仓库根目录的绝对路径。
+2. 验证 `threads/` 和 `skills/` 目录挂载正确。
+
+对于生产环境,使用命名卷或持久化卷声明(PVC)代替主机绑定挂载。
+
+## 生产部署注意事项
+
+### 沙箱模式选择
+
+| 沙箱 | 使用场景 |
+| -------------------------------------- | -------------------------- |
+| `LocalSandboxProvider` | 单用户、受信任的本地工作流 |
+| `AioSandboxProvider`(Docker) | 多用户、中等隔离需求 |
+| `AioSandboxProvider` + K8s Provisioner | 生产环境、强隔离、多用户 |
+
+对于有多个并发用户的任何部署,使用基于容器的沙箱,防止用户之间的执行环境相互干扰。
+
+### K8s Provisioner 配置
+
+
+
+#### 配置 Provisioner
+
+在你的 `.env` 或 compose 覆盖文件中设置必需的环境变量:
+
+```bash
+K8S_NAMESPACE=deer-flow
+SANDBOX_IMAGE=enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest
+DEER_FLOW_ROOT=/absolute/path/to/deer-flow
+```
+
+#### 配置沙箱提供者
+
+```yaml
+# config.yaml
+sandbox:
+ use: deerflow.community.aio_sandbox:AioSandboxProvider
+ provisioner_url: http://provisioner:8002
+```
+
+#### 配置数据持久化
+
+对于生产环境,使用 PVC 代替 hostPath 卷:
+
+```bash
+# 在 .env 或 compose 环境中
+USERDATA_PVC_NAME=deer-flow-userdata-pvc
+SKILLS_PVC_NAME=deer-flow-skills-pvc
+```
+
+当设置 `USERDATA_PVC_NAME` 时,Provisioner 自动使用子路径(`threads/{thread_id}/user-data`),每个线程在 PVC 中获得自己的目录。
+
+
+
+### nginx 配置
+
+nginx 路由所有流量,控制路由的关键环境变量:
+
+| 变量 | 默认值 | 描述 |
+| -------------------- | ---------------- | ----------------------------- |
+| `LANGGRAPH_UPSTREAM` | `langgraph:2024` | LangGraph 服务地址 |
+| `LANGGRAPH_REWRITE` | `/` | LangGraph 路由的 URL 重写前缀 |
+
+这些在 Docker Compose 环境中设置,并在容器启动时由 `envsubst` 处理。
+
+### 认证配置
+
+DeerFlow 应用使用 [Better Auth](https://www.better-auth.com/) 进行会话管理。在生产环境中:
+
+1. 将 `BETTER_AUTH_SECRET` 设置为强随机字符串(最少 32 个字符)。
+2. 将 `BETTER_AUTH_URL` 设置为你的公开 URL(例如 `https://your-domain.com`)。
+
+```bash
+# 生成密钥
+openssl rand -base64 32
+```
+
+### 资源建议
+
+| 服务 | 最低配置 | 推荐配置 |
+| ------------------------- | ---------------- | ---------------- |
+| LangGraph(Agent 运行时) | 2 vCPU、4 GB RAM | 4 vCPU、8 GB RAM |
+| Gateway | 0.5 vCPU、512 MB | 1 vCPU、1 GB |
+| 前端 | 0.5 vCPU、512 MB | 1 vCPU、1 GB |
+| 沙箱容器(每会话) | 1 vCPU、1 GB | 2 vCPU、2 GB |
+
+## 部署后验证
+
+启动后验证部署:
+
+```bash
+# 检查 Gateway 健康状态
+curl http://localhost:8001/health
+
+# 检查 LangGraph 健康状态
+curl http://localhost:2024/ok
+
+# 通过 nginx 列出配置的模型(验证完整代理链)
+curl http://localhost:2026/api/models
+```
+
+正常工作的部署会从每个端点返回 `200` 响应。`/api/models` 调用会返回你 `config.yaml` 中的模型列表。
+
+
+
+
+
diff --git a/frontend/src/content/zh/application/index.mdx b/frontend/src/content/zh/application/index.mdx
new file mode 100644
index 0000000000..81e7113e23
--- /dev/null
+++ b/frontend/src/content/zh/application/index.mdx
@@ -0,0 +1,72 @@
+---
+title: DeerFlow 应用
+description: DeerFlow 应用是 DeerFlow 生产体验的参考实现。它将 Harness 运行时、基于 Web 的对话工作区、API Gateway 和反向代理组合成一个可部署的完整系统。
+---
+
+import { Callout, Cards } from "nextra/components";
+
+# DeerFlow 应用
+
+
+ DeerFlow 应用是构建在 DeerFlow Harness 之上的完整 Super Agent
+ 应用。它将运行时能力打包成一个可部署的产品,包含 Web 界面、API Gateway
+ 和运维工具。
+
+
+DeerFlow 应用是 DeerFlow 生产体验的参考实现。它将 Harness 运行时、基于 Web 的对话工作区、API Gateway 和反向代理组合成一个可部署的完整系统。
+
+## 应用提供什么
+
+| 能力 | 描述 |
+| ---------------- | ----------------------------------------------------- |
+| **Web 工作区** | 浏览器对话界面,支持线程、产出物、文件上传和技能选择 |
+| **自定义 Agent** | 创建和管理具有不同模型、技能和工具集的命名 Agent |
+| **线程管理** | 带检查点和历史记录的持久化对话线程 |
+| **流式响应** | 实时 token 流式传输,带思考步骤和工具调用可见性 |
+| **产出物查看器** | Agent 生成文件和输出的浏览器内预览和下载 |
+| **扩展界面** | 无需编辑配置文件即可启用/禁用 MCP 服务器和技能 |
+| **Gateway API** | 桥接前端和 LangGraph 运行时的基于 FastAPI 的 REST API |
+
+## 架构
+
+DeerFlow 应用以四个服务的形式运行,通过单个 nginx 反向代理提供:
+
+```
+ ┌──────────────────┐
+ 浏览器 → │ nginx :2026 │
+ └──────────────────┘
+ │ │
+ ┌────────┘ └────────┐
+ ▼ ▼
+┌──────────────────┐ ┌──────────────────────┐
+│ 前端 :3000 │ │ Gateway API :8001 │
+│ (Next.js) │ │ (FastAPI) │
+└──────────────────┘ └──────────────────────┘
+ │
+ ┌─────────┘
+ ▼
+ ┌──────────────────────┐
+ │ LangGraph :2024 │
+ │ (DeerFlow Harness) │
+ └──────────────────────┘
+```
+
+- **nginx**:路由请求——`/api/*` 到 Gateway,LangGraph 流式端点到 LangGraph,其余到前端。
+- **前端**(Next.js + React):浏览器界面,与 Gateway 和 LangGraph 通信。
+- **Gateway**(FastAPI):处理 API 操作——模型列表、Agent CRUD、记忆、扩展管理、文件上传。
+- **LangGraph**:DeerFlow Harness 运行时,管理线程状态、Agent 执行和流式传输。
+
+## 技术栈
+
+| 层次 | 技术 |
+| ------------ | ------------------------------------------------------- |
+| 前端 | Next.js 16、React 19、TypeScript、pnpm |
+| Gateway | FastAPI、Python 3.12、uvicorn |
+| Agent 运行时 | LangGraph、LangChain、DeerFlow Harness |
+| 反向代理 | nginx |
+| 状态持久化 | LangGraph Server(默认)+ 可选 SQLite/PostgreSQL 检查点 |
+
+
+
+
+
diff --git a/frontend/src/content/zh/application/operations-and-troubleshooting.mdx b/frontend/src/content/zh/application/operations-and-troubleshooting.mdx
new file mode 100644
index 0000000000..c047bbd5cd
--- /dev/null
+++ b/frontend/src/content/zh/application/operations-and-troubleshooting.mdx
@@ -0,0 +1,179 @@
+---
+title: 运维与排障
+description: 本页面涵盖运行 DeerFlow 应用的操作信息:日志记录、常见问题和维护任务。
+---
+
+import { Callout, Cards } from "nextra/components";
+
+# 运维与排障
+
+本页面涵盖运行 DeerFlow 应用的操作信息:日志记录、常见问题和维护任务。
+
+## 日志
+
+DeerFlow 应用在 `logs/` 目录中写入每个服务的日志:
+
+| 文件 | 内容 |
+| -------------------- | -------------------------------------- |
+| `logs/langgraph.log` | Agent 运行时、工具调用、LangGraph 错误 |
+| `logs/gateway.log` | API 请求/响应、Gateway 错误 |
+| `logs/frontend.log` | Next.js 服务器日志 |
+| `logs/nginx.log` | 代理访问和错误日志 |
+
+**实时追踪日志**:
+
+```bash
+tail -f logs/langgraph.log # 查看 Agent 活动
+tail -f logs/gateway.log # 查看 API 请求
+```
+
+**调整日志级别**:
+
+```yaml
+# config.yaml
+log_level: debug # debug | info | warning | error
+```
+
+## 健康检查
+
+DeerFlow 暴露健康检查端点:
+
+```bash
+# Gateway 健康状态
+curl http://localhost:8001/health
+
+# LangGraph 健康状态
+curl http://localhost:2024/ok
+
+# 通过 nginx 完整代理链验证
+curl http://localhost:2026/api/models
+```
+
+## 配置升级
+
+当 `config.yaml` schema 更新时(由 `config_version` 字段标识),运行:
+
+```bash
+make config-upgrade
+```
+
+这将 `config.example.yaml` 中的新字段合并到你现有的 `config.yaml` 中,而不覆盖你的自定义内容。
+
+## 常见问题
+
+### 模型配置错误
+
+**症状**:Agent 在响应第一条消息时报错,日志中有 API 认证错误。
+
+**诊断**:
+
+```bash
+# 检查 LangGraph 日志中的模型错误
+grep -i "error\|apikey\|unauthorized" logs/langgraph.log | tail -20
+```
+
+**解决**:
+
+1. 验证 `config.yaml` 中 API key 字段名称正确(例如 `$OPENAI_API_KEY`)。
+2. 确认对应的环境变量已设置(`echo $OPENAI_API_KEY`)。
+3. 检查 `base_url`(如有)是否与提供商的实际端点匹配。
+
+---
+
+### 沙箱权限问题
+
+**症状**:工具报"文件未找到"或权限错误,即使 Agent 声称已创建文件。
+
+**诊断**:
+
+```bash
+# 检查线程用户数据目录是否存在且可写
+ls -la backend/.deer-flow/threads/
+```
+
+**解决**:
+
+1. 确保 `backend/.deer-flow/` 对运行 DeerFlow 的进程可写。
+2. 在 Docker 部署中,验证卷挂载路径正确(`DEER_FLOW_ROOT` 设置为绝对路径)。
+3. 如果使用基于容器的沙箱,确认 Docker 已运行并且容器镜像已拉取。
+
+---
+
+### 前端构建失败
+
+**症状**:`make install` 或前端构建步骤失败,提示 `BETTER_AUTH_SECRET` 错误。
+
+**解决**:
+
+```bash
+# 选项 1:设置环境变量(推荐)
+export BETTER_AUTH_SECRET=your-secret-here-at-least-32-chars
+cd frontend && pnpm build
+
+# 选项 2:跳过构建时验证(仅限开发)
+SKIP_ENV_VALIDATION=1 pnpm build
+```
+
+---
+
+### MCP 服务器连接失败
+
+**症状**:MCP 工具未出现,`logs/langgraph.log` 中有超时错误。
+
+**诊断**:
+
+```bash
+# 检查 MCP 相关错误
+grep -i "mcp\|timeout" logs/langgraph.log | tail -20
+```
+
+**解决**:
+
+1. 验证 `extensions_config.json` 中 MCP 服务器的 `command` 和 `args` 在服务器外部正常工作(手动运行命令)。
+2. 确认 MCP 服务器的依赖(如 `npx`、`uvx`)已安装并在 PATH 中。
+3. 检查 MCP 服务器是否需要网络访问或特定环境变量。
+
+---
+
+### K8s Provisioner 连接失败
+
+**症状**:沙箱工具请求挂起,日志中有连接拒绝错误。
+
+**解决**:
+
+1. 验证 `config.yaml` 中 `provisioner_url` 正确且 Provisioner Pod 运行正常。
+2. 检查 `K8S_NAMESPACE` 和 RBAC 配置是否允许 Provisioner 创建 Pod。
+3. 验证 `SANDBOX_IMAGE` 可以从 K8s 节点拉取。
+
+## 数据备份
+
+DeerFlow 将持久化数据存储在:
+
+- **线程数据**:`backend/.deer-flow/threads/` — 每个线程的上传文件、输出和工作区文件
+- **检查点**:取决于检查点器配置(SQLite:`backend/.deer-flow/checkpoints.db`,Redis:外部存储)
+- **记忆**:`backend/.deer-flow/memory.json`(以及 `agents/*/memory.json`)
+- **自定义 Agent 配置**:`backend/agents/*/config.yaml`
+
+对于生产部署,定期备份这些目录。Docker 部署中,确保这些目录绑定挂载到持久卷,而不是容器内部。
+
+## 停止和重启服务
+
+```bash
+# 停止所有本地服务
+make stop
+
+# 重启(停止后重新启动)
+make stop && make dev
+```
+
+在 Docker 中:
+
+```bash
+docker compose -f docker/docker-compose-dev.yaml down
+docker compose -f docker/docker-compose-dev.yaml up
+```
+
+
+
+
+
diff --git a/frontend/src/content/zh/application/quick-start.mdx b/frontend/src/content/zh/application/quick-start.mdx
new file mode 100644
index 0000000000..5ccf117adb
--- /dev/null
+++ b/frontend/src/content/zh/application/quick-start.mdx
@@ -0,0 +1,130 @@
+---
+title: 快速上手
+description: 本指南引导你使用 `make dev` 工作流在本地机器上启动 DeerFlow 应用。所有四个服务(LangGraph、Gateway、前端、nginx)一起启动,通过单个 URL 访问。
+---
+
+import { Callout, Cards, Steps } from "nextra/components";
+
+# 快速上手
+
+
+ 大约 10 分钟即可在本地运行 DeerFlow 应用。你需要一台安装了 Python
+ 3.12+、Node.js 22+ 的机器,以及至少一个 LLM API Key。
+
+
+本指南引导你使用 `make dev` 工作流在本地机器上启动 DeerFlow 应用。所有四个服务(LangGraph、Gateway、前端、nginx)一起启动,通过单个 URL 访问。
+
+## 前置条件
+
+检查所有必需工具是否已安装:
+
+```bash
+make check
+```
+
+必需工具:
+
+| 工具 | 最低版本 |
+| ------- | ------------ |
+| Python | 3.12 |
+| uv | 最新版 |
+| Node.js | 22 |
+| pnpm | 10 |
+| nginx | 任何近期版本 |
+
+在 macOS 上,使用 `brew install python uv node pnpm nginx` 安装。在 Linux 上,使用你的发行版包管理器。
+
+## 步骤
+
+
+
+### 克隆仓库
+
+```bash
+git clone https://github.com/bytedance/deer-flow.git
+cd deer-flow
+```
+
+### 安装依赖
+
+```bash
+make install
+```
+
+这会安装后端 Python 依赖(通过 `uv`)和前端 Node.js 依赖(通过 `pnpm`)。
+
+### 创建配置文件
+
+```bash
+cp config.example.yaml config.yaml
+```
+
+然后编辑 `config.yaml` 至少添加一个模型:
+
+```yaml
+models:
+ - name: gpt-4o
+ use: langchain_openai:ChatOpenAI
+ model: gpt-4o
+ api_key: $OPENAI_API_KEY
+ request_timeout: 600.0
+ max_retries: 2
+ supports_vision: true
+```
+
+启动前设置对应的环境变量:
+
+```bash
+export OPENAI_API_KEY=sk-...
+```
+
+查看[配置](/docs/application/configuration)页面了解其他模型提供商的示例。
+
+### 启动所有服务
+
+```bash
+make dev
+```
+
+这会启动:
+
+- LangGraph 服务,端口 `2024`
+- Gateway API,端口 `8001`
+- 前端,端口 `3000`
+- nginx 反向代理,端口 `2026`
+
+在浏览器中打开 [http://localhost:2026](http://localhost:2026)。
+
+### 停止所有服务
+
+```bash
+make stop
+```
+
+
+
+## `make dev` 做了什么
+
+- 首先停止已有的服务进程(在启动被中断后安全运行)。
+- 每个服务在后台启动,日志写入 `logs/` 目录。
+- nginx 通过端口 `2026` 代理所有流量,所以你只需要一个 URL。
+
+日志文件:
+
+| 服务 | 日志文件 |
+| --------- | -------------------- |
+| LangGraph | `logs/langgraph.log` |
+| Gateway | `logs/gateway.log` |
+| 前端 | `logs/frontend.log` |
+| nginx | `logs/nginx.log` |
+
+
+ 如果有问题,先检查日志文件。大多数启动错误(缺失 API
+ Key、配置解析失败)会出现在 logs/langgraph.log 或{" "}
+ logs/gateway.log 中。
+
+
+
+
+
+
diff --git a/frontend/src/content/zh/application/workspace-usage.mdx b/frontend/src/content/zh/application/workspace-usage.mdx
new file mode 100644
index 0000000000..e4e3fb541b
--- /dev/null
+++ b/frontend/src/content/zh/application/workspace-usage.mdx
@@ -0,0 +1,102 @@
+---
+title: 工作区使用
+description: DeerFlow 工作区是一个基于浏览器的对话界面,你可以在其中向 Agent 发送消息、上传文件、查看中间步骤,以及下载生成的产出物。
+---
+
+import { Callout, Cards } from "nextra/components";
+
+# 工作区使用
+
+
+ DeerFlow 工作区是你与 Agent
+ 交互的地方。本页面涵盖主要用户界面工作流——创建对话、上传文件、查看产出物和使用技能。
+
+
+DeerFlow 工作区是一个基于浏览器的对话界面,你可以在其中向 Agent 发送消息、上传文件、查看中间步骤,以及下载生成的产出物。
+
+## 新建对话
+
+1. 打开 [http://localhost:2026](http://localhost:2026)。
+2. 在主界面点击 **New Thread**(新建线程)按钮,或直接在输入框中输入消息。
+3. 输入你的第一条消息并发送。
+
+每个对话是一个**线程**,有独立的历史记录、产出物和检查点。
+
+## 选择模型
+
+在消息输入区域,点击**模型选择器**从 `config.yaml` 中配置的模型中选择。默认选中第一个配置的模型。
+
+你可以在对话中途切换模型——每次新的发送都会使用所选的模型。
+
+## 选择 Agent
+
+打开 **Agent 选择器**从可用 Agent 中选择:
+
+- **默认 Agent**:使用所有全局工具和技能的通用 Agent。
+- **自定义 Agent**:具有专门技能组合、工具访问和提示词的命名 Agent。
+
+参见 [Agent 与线程](/docs/application/agents-and-threads)页面了解如何创建自定义 Agent。
+
+## 上传文件
+
+拖放文件到消息输入区域,或点击附件图标上传。上传的文件挂载在沙箱的 `/mnt/user-data/uploads/` 路径下,Agent 可以直接读取和处理。
+
+**支持的操作**:
+
+- 读取和分析文件内容
+- 处理数据文件(CSV、JSON、Excel)
+- 提取 PDF 内容
+- 分析图像(需要支持视觉的模型)
+
+
+ 上传大文件时,告诉 Agent
+ 文件的具体内容,以便获得更好的结果(例如"分析这个包含季度销售数据的 CSV")。
+
+
+## 使用技能
+
+某些 Agent 配置暴露了**技能选择器**。技能告诉 Agent 要执行哪类工作(深度研究、数据分析、图表生成等)。
+
+在输入框中点击技能选择器选择一个技能。选定的技能会将专业指令和工作流注入到当前对话中。
+
+## 查看 Agent 思考过程
+
+当 Agent 调用工具或进行推理时,你可以展开**思考步骤**:
+
+- **工具调用**:Agent 正在调用哪个工具、使用什么参数。
+- **工具结果**:工具返回了什么。
+- **思考内容**(如果模型支持):模型的内部推理过程。
+- **待办列表**(计划模式):当前任务列表及其完成状态。
+
+点击消息旁边的展开箭头查看完整的推理链。
+
+## 查看产出物
+
+当 Agent 生成文件(报告、图表、代码文件、演示文稿)时,它们会以**产出物**的形式出现在对话中。
+
+点击产出物卡片:
+
+- 在浏览器中预览文件。
+- 下载文件到本地机器。
+- 复制文件内容。
+
+产出物会持久保存在线程的用户数据目录中(`.deer-flow/threads/{thread_id}/user-data/outputs/`)。
+
+## 管理线程
+
+**查看过往线程**:使用侧边栏浏览之前的对话。
+
+**重命名线程**:`TitleMiddleware` 会在第一次交互后自动生成标题。你也可以手动重命名线程。
+
+**删除线程**:从线程侧边栏菜单中选择删除。这会移除线程状态和所有相关的用户数据文件。
+
+
+
+
+
diff --git a/frontend/src/content/zh/harness/_meta.ts b/frontend/src/content/zh/harness/_meta.ts
new file mode 100644
index 0000000000..e1934f5ad0
--- /dev/null
+++ b/frontend/src/content/zh/harness/_meta.ts
@@ -0,0 +1,48 @@
+import type { MetaRecord } from "nextra";
+
+const meta: MetaRecord = {
+ index: {
+ title: "安装",
+ },
+ "quick-start": {
+ title: "快速上手",
+ },
+ "design-principles": {
+ title: "设计理念",
+ },
+ "lead-agent": {
+ title: "Lead Agent",
+ },
+ middlewares: {
+ title: "中间件",
+ },
+ configuration: {
+ title: "配置",
+ },
+ memory: {
+ title: "记忆系统",
+ },
+ tools: {
+ title: "工具",
+ },
+ skills: {
+ title: "技能",
+ },
+ sandbox: {
+ title: "沙箱",
+ },
+ subagents: {
+ title: "子 Agent",
+ },
+ mcp: {
+ title: "MCP 集成",
+ },
+ customization: {
+ title: "自定义与扩展",
+ },
+ "integration-guide": {
+ title: "集成指南",
+ },
+};
+
+export default meta;
diff --git a/frontend/src/content/zh/harness/configuration.mdx b/frontend/src/content/zh/harness/configuration.mdx
new file mode 100644
index 0000000000..1b5cf02074
--- /dev/null
+++ b/frontend/src/content/zh/harness/configuration.mdx
@@ -0,0 +1,160 @@
+---
+title: 配置
+description: DeerFlow 的配置系统围绕一个目标设计:每一个有意义的行为都应该可以在配置文件中表达,而不是硬编码在应用程序中。这使部署可重现、可审计,并且易于按环境定制。
+---
+
+import { Callout, Cards } from "nextra/components";
+
+# 配置
+
+
+ 所有 DeerFlow Harness 行为都由 config.yaml{" "}
+ 驱动。一个文件控制哪些模型可用、沙箱如何运行、加载哪些工具,以及每个子系统的行为。
+
+
+DeerFlow 的配置系统围绕一个目标设计:每一个有意义的行为都应该可以在配置文件中表达,而不是硬编码在应用程序中。这使部署可重现、可审计,并且易于按环境定制。
+
+## 配置文件位置
+
+DeerFlow 使用以下优先级顺序解析 `config.yaml`:
+
+1. 显式传递给 `AppConfig.from_file(config_path)` 的路径。
+2. `DEER_FLOW_CONFIG_PATH` 环境变量。
+3. `backend/config.yaml`(相对于后端目录)。
+4. 仓库根目录中的 `config.yaml`。
+
+如果这些路径都不存在,应用程序在启动时会报错。
+
+要使用自定义位置:
+
+```bash
+export DEER_FLOW_CONFIG_PATH=/path/to/my-config.yaml
+```
+
+## 环境变量插值
+
+任何字段值都可以使用 `$VAR_NAME` 语法引用环境变量:
+
+```yaml
+models:
+ - name: gpt-4o
+ api_key: $OPENAI_API_KEY
+```
+
+这使密钥不出现在配置文件本身中,变量在运行时从进程环境解析。
+
+## `use` 字段
+
+许多配置条目使用 `use:` 字段来指定要实例化的 Python 类或对象,格式为:
+
+```
+package.subpackage.module:ClassName
+```
+
+或对于模块级对象:
+
+```
+package.subpackage.module:variable_name
+```
+
+示例:
+
+```yaml
+sandbox:
+ use: deerflow.sandbox.local:LocalSandboxProvider
+
+tools:
+ - use: deerflow.community.tavily.tools:web_search_tool
+ api_key: $TAVILY_API_KEY
+```
+
+这是 DeerFlow 实现可插拔性而不硬编码类引用的方式。
+
+## 额外字段透传
+
+对于模型配置,`ModelConfig` 使用 `pydantic ConfigDict(extra="allow")`。这意味着你在模型条目下添加的任何额外字段都直接传递给模型构造函数。这允许提供商特定选项(如 `extra_body`、`reasoning` 或自定义超时键)无需修改 Harness 即可工作:
+
+```yaml
+models:
+ - name: my-model
+ use: langchain_openai:ChatOpenAI
+ model: gpt-4o
+ api_key: $OPENAI_API_KEY
+ some_provider_specific_option: value # 传递给 ChatOpenAI 构造函数
+```
+
+## 配置版本
+
+`config.yaml` 包含追踪 schema 版本的 `config_version` 字段:
+
+```yaml
+config_version: 6
+```
+
+当 schema 更改(新字段、重命名章节)时,此数字增加。如果你本地的 `config.yaml` 落后于当前版本,运行:
+
+```bash
+make config-upgrade
+```
+
+这将 `config.example.yaml` 中的新字段合并到你现有的 `config.yaml` 中,而不覆盖你的自定义内容。
+
+## 模块配置速查表
+
+下表将 `config.yaml` 中的每个顶层章节映射到其文档页面:
+
+| 章节 | 描述 | 文档 |
+| ----------------- | -------------------------------------------- | ---------------------------------------------------- |
+| `log_level` | 日志级别(`debug`/`info`/`warning`/`error`) | — |
+| `models` | 可用的 LLM 模型 | [Lead Agent](/docs/harness/lead-agent) |
+| `token_usage` | 每次模型调用的 token 追踪 | [中间件](/docs/harness/middlewares) |
+| `tools` | 可用的 Agent 工具 | [工具](/docs/harness/tools) |
+| `tool_groups` | 工具的命名分组 | [工具](/docs/harness/tools) |
+| `tool_search` | 延迟/按需工具加载 | [工具](/docs/harness/tools) |
+| `sandbox` | 沙箱提供者和选项 | [沙箱](/docs/harness/sandbox) |
+| `skills` | 技能目录和容器路径 | [技能](/docs/harness/skills) |
+| `skill_evolution` | Agent 管理的技能创建 | [技能](/docs/harness/skills) |
+| `subagents` | 子 Agent 超时和最大轮次 | [子 Agent](/docs/harness/subagents) |
+| `acp_agents` | 外部 ACP Agent 集成 | [子 Agent](/docs/harness/subagents) |
+| `memory` | 跨会话记忆存储 | [记忆系统](/docs/harness/memory) |
+| `summarization` | 对话摘要压缩 | [中间件](/docs/harness/middlewares) |
+| `title` | 自动生成线程标题 | [中间件](/docs/harness/middlewares) |
+| `checkpointer` | 线程状态持久化 | [Agent 与线程](/docs/application/agents-and-threads) |
+| `guardrails` | 工具调用授权 | — |
+| `uploads` | 文件上传设置(PDF 转换器) | — |
+| `channels` | IM 频道集成(飞书、Slack 等) | — |
+
+## 最小配置示例
+
+最小有效的 `config.yaml` 至少需要一个模型和一个沙箱:
+
+```yaml
+config_version: 6
+
+models:
+ - name: gpt-4o
+ use: langchain_openai:ChatOpenAI
+ model: gpt-4o
+ api_key: $OPENAI_API_KEY
+ request_timeout: 600.0
+ max_retries: 2
+ supports_vision: true
+
+sandbox:
+ use: deerflow.sandbox.local:LocalSandboxProvider
+
+tools:
+ - use: deerflow.community.ddg_search.tools:web_search_tool
+ - use: deerflow.community.jina_ai.tools:web_fetch_tool
+ - use: deerflow.sandbox.tools:ls_tool
+ - use: deerflow.sandbox.tools:read_file_tool
+ - use: deerflow.sandbox.tools:write_file_tool
+ - use: deerflow.sandbox.tools:bash_tool
+```
+
+从仓库根目录的 `config.example.yaml` 开始,取消注释你需要的章节。
+
+
+
+
+
diff --git a/frontend/src/content/zh/harness/customization.mdx b/frontend/src/content/zh/harness/customization.mdx
new file mode 100644
index 0000000000..3b77c03948
--- /dev/null
+++ b/frontend/src/content/zh/harness/customization.mdx
@@ -0,0 +1,157 @@
+---
+title: 自定义与扩展
+description: DeerFlow 的可插拔架构意味着系统的大多数部分都可以在不 fork 核心的情况下被替换或扩展。本页面列举了扩展点,并解释如何使用每一个。
+---
+
+import { Callout, Cards } from "nextra/components";
+
+# 自定义与扩展
+
+
+ DeerFlow
+ 设计为可适配的。你可以通过编写自定义中间件、添加新工具、构建技能包以及通过
+ config.yaml 的 use: 字段替换任何内置组件来扩展 Agent 行为。
+
+
+DeerFlow 的可插拔架构意味着系统的大多数部分都可以在不 fork 核心的情况下被替换或扩展。本页面列举了扩展点,并解释如何使用每一个。
+
+## 自定义中间件
+
+中间件是为 Lead Agent 添加行为的主要扩展点。它们包裹每次 LLM 调用,可以在模型调用前后读取和修改 Agent 的状态。
+
+添加自定义中间件:
+
+1. 实现 `langchain.agents.middleware` 中的 `AgentMiddleware` 接口。
+2. 在构建 Agent 时通过 `custom_middlewares` 参数传入你的中间件。
+
+```python
+from langchain.agents.middleware import AgentMiddleware
+from deerflow.agents.thread_state import ThreadState
+
+class AuditMiddleware(AgentMiddleware):
+ async def on_start(self, state: ThreadState, config):
+ # 在每次模型调用前运行
+ print(f"[审计] 轮次开始:上下文中有 {len(state.messages)} 条消息")
+ return state, config
+
+ async def on_end(self, state: ThreadState, config):
+ # 在每次模型调用后运行
+ print(f"[审计] 轮次结束:最后一条消息类型 = {state.messages[-1].type}")
+ return state, config
+```
+
+自定义中间件在链末尾 `ClarificationMiddleware` 之前注入,后者始终最后运行。
+
+## 自定义工具
+
+在 `config.yaml` 的 `tools:` 下注册新工具,将其添加到 Agent:
+
+```yaml
+tools:
+ - use: mypackage.tools:my_custom_tool
+ api_key: $MY_TOOL_API_KEY
+```
+
+你的工具必须是 LangChain `BaseTool` 或用 `@tool` 装饰的函数。它将使用 `use:` 类路径和配置条目中的任何额外字段实例化。
+
+```python
+# mypackage/tools.py
+from langchain_core.tools import tool
+
+@tool
+def my_custom_tool(query: str) -> str:
+ """搜索我的自定义数据源。"""
+ return do_search(query)
+```
+
+## 自定义沙箱提供者
+
+沙箱可以通过实现 `SandboxProvider` 接口来替换:
+
+```python
+from deerflow.sandbox.sandbox_provider import SandboxProvider
+
+class MyCustomSandboxProvider(SandboxProvider):
+ def acquire(self, thread_id: str | None = None) -> str:
+ # 返回 sandbox_id
+ ...
+
+ def get(self, sandbox_id: str):
+ # 返回此 id 的沙箱实例
+ ...
+
+ def release(self, sandbox_id: str) -> None:
+ # 清理
+ ...
+```
+
+然后在 `config.yaml` 中引用:
+
+```yaml
+sandbox:
+ use: mypackage.sandbox:MyCustomSandboxProvider
+```
+
+## 自定义记忆存储
+
+通过实现 `MemoryStorage` 将基于文件的记忆替换为任何持久化存储:
+
+```python
+from deerflow.agents.memory.storage import MemoryStorage
+from typing import Any
+
+class RedisMemoryStorage(MemoryStorage):
+ def load(self, agent_name: str | None = None) -> dict[str, Any]:
+ ...
+
+ def reload(self, agent_name: str | None = None) -> dict[str, Any]:
+ ...
+
+ def save(self, memory_data: dict[str, Any], agent_name: str | None = None) -> bool:
+ ...
+```
+
+在 `config.yaml` 中配置:
+
+```yaml
+memory:
+ storage_class: mypackage.storage:RedisMemoryStorage
+```
+
+## 自定义技能
+
+技能是最简单的扩展点。在 `skills/custom/your-skill-name/` 下创建一个目录,并在其中添加 `SKILL.md` 文件。技能将在下次 `load_skills()` 调用时自动被发现。
+
+参见[技能](/docs/harness/skills)页面了解完整的目录结构和 `SKILL.md` 格式。
+
+## 自定义模型
+
+任何与 LangChain 兼容的聊天模型都可以通过在 `use:` 字段中指定来使用:
+
+```yaml
+models:
+ - name: my-custom-model
+ use: mypackage.models:MyCustomChatModel
+ # 任何额外字段都作为 kwargs 传递给构造函数
+ base_url: http://my-model-server:8080
+ api_key: $MY_MODEL_API_KEY
+```
+
+模型类必须实现 LangChain 的 `BaseChatModel` 接口。
+
+## 自定义检查点
+
+线程状态持久化可以使用任何与 LangGraph 兼容的检查点器:
+
+```yaml
+checkpointer:
+ type: sqlite
+ connection_string: ./my-checkpoints.db
+```
+
+对于自定义后端,实现 LangGraph 的 `BaseCheckpointSaver` 接口,并在初始化 `DeerFlowClient` 时以编程方式配置它。
+
+
+
+
+
diff --git a/frontend/src/content/zh/harness/design-principles.mdx b/frontend/src/content/zh/harness/design-principles.mdx
new file mode 100644
index 0000000000..97498a91c1
--- /dev/null
+++ b/frontend/src/content/zh/harness/design-principles.mdx
@@ -0,0 +1,118 @@
+---
+title: 设计理念
+description: 了解 DeerFlow Harness 背后的设计理念,有助于你有效地使用它、自信地扩展它,并推断 Agent 在生产环境中的行为方式。
+---
+
+import { Callout, Cards } from "nextra/components";
+
+# 设计理念
+
+
+ DeerFlow 围绕一个核心思想构建:Agent
+ 行为应该由小型、可观察、可替换的组件组合而成——而不是硬编码到固定的工作流图中。
+
+
+了解 DeerFlow Harness 背后的设计理念,有助于你有效地使用它、自信地扩展它,并推断 Agent 在生产环境中的行为方式。
+
+## 为什么是 Harness,而非 Framework
+
+框架提供抽象和构建块,你负责组装各部分并编写连接它们的胶水代码。
+
+**Harness** 更进一步。它打包了一个有主张的、可直接运行的运行时,让 Agent 无需你每次重建相同的基础设施就能完成真实工作。
+
+DeerFlow 之所以是 Harness,是因为它内置了:
+
+- 带有工具路由的 Lead Agent
+- 包裹每次 LLM 调用的中间件链
+- 用于文件和命令的沙箱执行
+- 按需加载专业能力的技能
+- 用于委派并行工作的子 Agent
+- 跨会话连续性的记忆
+- 控制这一切的配置系统
+
+你不需要从头设计编排层——Harness 就是编排层。
+
+## 长时序任务是主要使用场景
+
+DeerFlow 专为需要不止一次提示-响应交互的任务而设计。一个有用的长时序 Agent 必须:
+
+1. 制定计划
+2. 按顺序调用工具
+3. 检查和修改文件
+4. 在失败时恢复
+5. 当任务太宽泛时委派给子 Agent
+6. 最终返回具体的产出物
+
+DeerFlow 中的每一个架构决策都以这个使用场景为标准进行评估。短暂、无状态的交互很简单。在真实世界压力下的长时序、多步骤工作流才是目标。
+
+## 中间件链优于继承
+
+DeerFlow 不要求你子类化 Agent 或覆盖方法来改变其行为。相反,它使用一个**中间件链**来包裹每次 LLM 调用。
+
+每个中间件都是一个小型、专注的插件,可以在模型调用前后检查或修改 Agent 的状态。Lead Agent 的行为完全由活跃的中间件决定。
+
+这种设计有几个优点:
+
+- 各个行为(记忆、摘要、澄清、循环检测)相互隔离,可独立测试。
+- 可以扩展链而无需触碰 Agent 的核心逻辑。
+- 每个中间件的效果是可见且可审计的,因为它只触及其声明的状态。
+
+详见[中间件](/docs/harness/middlewares)页面的完整列表和配置说明。
+
+## 技能提供专业化但不污染上下文
+
+**技能**是面向任务的能力包,包含指令、工作流、最佳实践以及让 Agent 在特定工作类型中有效的工具或资源。
+
+关键设计决策是技能**按需加载**。基础 Agent 保持通用。当任务需要深度研究时,加载研究技能;当任务需要数据分析时,加载分析技能。
+
+这很重要,因为它保持了基础 Agent 上下文的清晰。为撰写学术论文而设计的专业提示词不会污染专注于编码的会话。技能在相关时注入内容,仅此而已。
+
+## 沙箱是执行环境
+
+DeerFlow 为 Agent 提供一个**沙箱**:一个隔离的工作区,Agent 可以在其中读取文件、写入输出、运行命令并生成产出物。
+
+这将 Agent 从文本生成器转变为能够真正完成工作的系统。Agent 不只是描述要写什么代码,而是可以写代码、运行代码并验证结果。
+
+隔离性很重要,因为执行应该可重现且可控。沙箱是 DeerFlow 支持真实行动而不仅仅是对话的原因。
+
+## 上下文工程让长时序任务可控
+
+上下文压力是长时序 Agent 的主要挑战。如果所有内容无限期累积在上下文窗口中,Agent 会变得越来越慢、越来越嘈杂、越来越不可靠。
+
+DeerFlow 通过**上下文工程**来解决这个问题——有意识地控制 Agent 在每个步骤中看到、记住和忽略什么:
+
+- **摘要压缩**:当对话变得太长时,旧轮次被摘要替代。Agent 保留含义而不是全量内容。
+- **子 Agent 上下文隔离**:当工作委派给子 Agent 时,子 Agent 只接收完成其任务所需的信息,而不是完整的父历史。
+- **外部工作记忆**:任务产生的文件和产出物保存在磁盘上,而不在上下文窗口中。Agent 在需要时引用它们。
+- **记忆注入**:跨会话事实以受控的 token 预算注入到系统提示中。
+
+这是 DeerFlow 中最重要的思想之一。好的 Agent 行为不仅仅是关于更强大的模型,也关于在正确时间给模型提供正确的工作集。
+
+## 配置驱动行为
+
+DeerFlow 中所有有意义的行为都通过 `config.yaml` 控制。系统的设计使运维人员无需触碰代码就能改变 Agent 的行为——使用哪些模型、是否启用摘要压缩、如何限制子 Agent、哪些工具可用。
+
+这个设计原则有三个含义:
+
+1. **可重现性**:一个配置文件在某时间点完整描述了 Agent 的行为。
+2. **可部署性**:相同的代码在不同环境中运行方式不同,因为配置不同。
+3. **可审计性**:Agent 能做什么和不能做什么在一处可见。
+
+环境变量插值(`api_key: $OPENAI_API_KEY`)将密钥排除在提交的配置文件之外,同时保持相同的结构。
+
+## 总结
+
+| 设计原则 | 实践含义 |
+| ---------------------- | ---------------------------------------- |
+| Harness 而非 Framework | 开箱即用的运行时,所有基础设施已预先连接 |
+| 长时序优先 | 架构假设多步骤、多工具、多轮次任务 |
+| 中间件优于继承 | 行为由小型、隔离的插件组合而成 |
+| 技能提供专业化 | 领域能力按需注入,保持基础干净 |
+| 沙箱用于执行 | 真实文件和命令操作的隔离工作区 |
+| 上下文工程 | 主动管理 Agent 所见内容以保持有效性 |
+| 配置驱动 | 所有关键行为通过 `config.yaml` 控制 |
+
+
+
+
+
diff --git a/frontend/src/content/zh/harness/index.mdx b/frontend/src/content/zh/harness/index.mdx
new file mode 100644
index 0000000000..6c57cd0101
--- /dev/null
+++ b/frontend/src/content/zh/harness/index.mdx
@@ -0,0 +1,54 @@
+---
+title: 安装 DeerFlow Harness
+description: DeerFlow Harness 是构建自己 Super Agent 系统的 Python SDK 和运行时基础。
+---
+
+import { Callout, Cards } from "nextra/components";
+
+# 安装 DeerFlow Harness
+
+
+ DeerFlow Harness Python 包将以 deerflow{" "}
+ 名称发布。目前尚未正式发布,安装方式即将推出。
+
+
+DeerFlow Harness 是构建自己 Super Agent 系统的 Python SDK 和运行时基础。
+
+如果你想在自己的产品或工作流中整合技能、记忆、工具、沙箱和子 Agent 等 Agent 能力,这正是你需要的 DeerFlow 组件。
+
+## 包名称
+
+包名将是:
+
+```bash
+pip install deerflow
+```
+
+该包目前还未公开发布,但这是文档正式发布后将使用的安装路径。
+
+## 当前状态
+
+DeerFlow Harness 包**即将推出**。
+
+目前,本章节的存在是为了建立 SDK 入口和包标识,同时公开分发流程正在最终确定中。
+
+## Harness 将提供什么
+
+Harness 专为想要在 DeerFlow 运行时模型之上构建自己 Agent 系统的开发者设计。
+
+它将提供以下基础:
+
+- 构建长时序 Agent
+- 组合运行时能力(记忆、工具、技能、子 Agent)
+- 在沙箱中执行 Agent
+- 通过配置和代码自定义 Agent 行为
+- 将 DeerFlow 集成到自己的应用架构中
+
+## 下一步
+
+在包正式发布之前,了解 DeerFlow Harness 的最佳方式是阅读本章节的概念和实现文档。
+
+
+
+
+
diff --git a/frontend/src/content/zh/harness/integration-guide.mdx b/frontend/src/content/zh/harness/integration-guide.mdx
new file mode 100644
index 0000000000..ea0a58c3f9
--- /dev/null
+++ b/frontend/src/content/zh/harness/integration-guide.mdx
@@ -0,0 +1,163 @@
+---
+title: 集成指南
+description: DeerFlow Harness 不仅仅是一个独立应用程序——它是一个可以导入并在你自己的后端、API 服务器、自动化系统或多 Agent 协调器中使用的 Python 库。
+---
+
+import { Callout, Cards } from "nextra/components";
+
+# 集成指南
+
+
+ DeerFlow Harness 可以嵌入任何 Python 应用程序。本指南涵盖在你自己的系统中将
+ DeerFlow 作为库使用的集成模式。
+
+
+DeerFlow Harness 不仅仅是一个独立应用程序——它是一个可以导入并在你自己的后端、API 服务器、自动化系统或多 Agent 协调器中使用的 Python 库。
+
+## 嵌入 DeerFlowClient
+
+主要集成点是 `DeerFlowClient`。它封装了 LangGraph 运行时,并提供一个简洁的 API,用于在任何 Python 应用中发送消息和流式传输响应。
+
+```python
+from deerflow.client import DeerFlowClient
+from deerflow.config import load_config
+
+# 加载配置(读取 config.yaml 或 DEER_FLOW_CONFIG_PATH)
+load_config()
+
+client = DeerFlowClient()
+```
+
+客户端是线程安全的,设计为实例化一次并在请求之间复用。
+
+## 异步流式传输
+
+推荐的集成模式是异步流式传输。这让你可以在 Agent 生成响应时实时访问每个 token 和事件:
+
+```python
+import asyncio
+
+async def run_agent(thread_id: str, user_message: str):
+ async for event in client.astream(
+ thread_id=thread_id,
+ message=user_message,
+ config={
+ "configurable": {
+ "model_name": "gpt-4o",
+ "subagent_enabled": True,
+ }
+ },
+ ):
+ # 处理每个流式事件
+ yield event
+
+# 在 FastAPI 处理器中:
+# from fastapi.responses import StreamingResponse
+# return StreamingResponse(run_agent(thread_id, message), media_type="text/event-stream")
+```
+
+## 非流式调用
+
+对于批处理或只需要最终结果的场景:
+
+```python
+async def run_agent_sync(thread_id: str, user_message: str) -> dict:
+ result = await client.ainvoke(
+ thread_id=thread_id,
+ message=user_message,
+ )
+ return result
+```
+
+## 线程管理
+
+线程表示持久化对话。使用唯一线程 ID 隔离不同用户会话:
+
+```python
+import uuid
+
+# 新对话
+thread_id = str(uuid.uuid4())
+
+# 继续已有对话(相同 thread_id)
+# 如果配置了检查点,Agent 将看到完整历史
+await client.ainvoke(thread_id=existing_thread_id, message="后续问题")
+```
+
+## 自定义 Agent 配置
+
+通过创建命名 Agent 配置并在运行时传入 `agent_name` 来构建领域特定 Agent:
+
+```python
+# agents/research-assistant/config.yaml 必须存在并包含技能和工具配置
+
+result = await client.ainvoke(
+ thread_id=thread_id,
+ message=user_message,
+ config={
+ "configurable": {
+ "agent_name": "research-assistant",
+ "model_name": "gpt-4o",
+ }
+ },
+)
+```
+
+## 与 FastAPI 集成
+
+DeerFlow Gateway 本身是一个 FastAPI 应用程序。你可以将其作为子应用程序挂载:
+
+```python
+from fastapi import FastAPI
+from deerflow.config import load_config
+
+load_config()
+
+app = FastAPI()
+
+# 挂载 DeerFlow Gateway
+from deerflow.app.gateway.main import app as gateway_app
+app.mount("/deerflow", gateway_app)
+```
+
+或者在你自己的 FastAPI 路由中直接使用 `DeerFlowClient` 进行流式传输:
+
+```python
+from fastapi import FastAPI
+from fastapi.responses import StreamingResponse
+from deerflow.client import DeerFlowClient
+
+app = FastAPI()
+client = DeerFlowClient()
+
+@app.post("/chat/{thread_id}")
+async def chat(thread_id: str, body: dict):
+ async def generate():
+ async for event in client.astream(thread_id=thread_id, message=body["message"]):
+ yield f"data: {event}\n\n"
+ return StreamingResponse(generate(), media_type="text/event-stream")
+```
+
+## 嵌入模式下的配置
+
+当嵌入到另一个应用程序时,显式设置配置路径以避免歧义:
+
+```python
+import os
+os.environ["DEER_FLOW_CONFIG_PATH"] = "/path/to/my-deerflow-config.yaml"
+
+from deerflow.config import load_config
+load_config()
+```
+
+或直接传递路径:
+
+```python
+from deerflow.config import load_config
+load_config(config_path="/path/to/my-deerflow-config.yaml")
+```
+
+
+
+
+
diff --git a/frontend/src/content/zh/harness/lead-agent.mdx b/frontend/src/content/zh/harness/lead-agent.mdx
new file mode 100644
index 0000000000..5e298a8096
--- /dev/null
+++ b/frontend/src/content/zh/harness/lead-agent.mdx
@@ -0,0 +1,134 @@
+---
+title: Lead Agent
+description: Lead Agent 是 DeerFlow 线程中的核心执行者。每个对话、任务和工作流都通过它进行。理解它的工作方式有助于你有效地配置它,并在需要时扩展它。
+---
+
+import { Callout, Cards, Steps } from "nextra/components";
+
+# Lead Agent
+
+
+ Lead Agent 是每个 DeerFlow
+ 线程中的主要推理和编排单元。它决定要做什么、调用工具、委派子
+ Agent,并返回产出物。
+
+
+Lead Agent 是 DeerFlow 线程中的核心执行者。每个对话、任务和工作流都通过它进行。理解它的工作方式有助于你有效地配置它,并在需要时扩展它。
+
+## Lead Agent 的职责
+
+Lead Agent 负责:
+
+- 接收用户消息并维护对话状态
+- 推断接下来要做什么(规划、工具选择、委派)
+- 调用工具——内置工具、社区工具、MCP 工具或技能工具
+- 通过 `task` 工具将子任务委派给子 Agent
+- 管理产出物(文件、输出、交付物)
+- 在计划模式下更新待办列表
+- 向用户返回最终响应或产出物
+
+Lead Agent 不硬编码特定的工作流。它使用模型的推理能力来适应用户提供的任何任务,由系统提示和当前范围内的技能引导。
+
+## 运行时基础
+
+Lead Agent 基于 **LangGraph** 和 **LangChain Agent** 原语构建:
+
+- `langchain.agents` 的 [`create_agent`](https://python.langchain.com/docs/concepts/agents/) 将 LLM 封装成工具调用 Agent 循环。
+- LangGraph 管理 `ThreadState`,提供检查点、流式传输和图执行模型。
+- **中间件链**包裹 Agent 循环的每一轮,提供记忆、摘要和澄清等跨领域能力。
+
+## 执行流程
+
+
+
+### 接收消息
+
+用户消息到达并添加到 `ThreadState.messages`。`ThreadState` 保存完整的对话历史、任何活跃的待办列表、累积的产出物和运行时元数据。
+
+### 中间件前处理
+
+在调用模型之前,每个活跃的中间件都有机会修改状态。例如,`MemoryMiddleware` 将持久化的记忆事实注入到系统提示中,`SummarizationMiddleware` 在 token 预算超出时可能压缩旧消息。
+
+### LLM 推理
+
+模型接收当前消息(包括带有活跃技能指令的系统提示),并产生直接回复或一个或多个工具调用请求。
+
+### 工具执行
+
+如果请求了工具调用,它们会被分发到相应的处理器——用于文件和命令操作的沙箱工具、用于网络访问的社区工具,或用于子 Agent 委派的 `task` 工具。
+
+### 中间件后处理
+
+工具结果返回后、下一次模型调用之前,中间件再次运行。`TitleMiddleware` 可能在第一次交互后生成线程标题,`TodoMiddleware` 可能更新任务列表。
+
+### 循环或响应
+
+如果模型需要更多信息(例如,工具返回了部分结果),循环继续。当模型决定任务完成时,它产生最终消息,循环结束。
+
+### 状态更新
+
+`ThreadState` 更新为新消息、产出物和记忆队列。如果配置了检查点,状态将被持久化。
+
+
+
+## 模型选择
+
+Lead Agent 在运行时使用以下优先级顺序解析要使用的模型:
+
+1. 每次请求配置中的 `model_name`(或 `model`),如果提供且有效。
+2. 活跃自定义 Agent 配置中的 `model` 字段,如果指定了 Agent。
+3. `config.yaml` 中 `models:` 列表的第一个模型(全局默认值)。
+
+如果请求的模型名称在配置中找不到,系统回退到默认模型并记录警告。
+
+```yaml
+models:
+ - name: my-primary-model
+ use: langchain_openai:ChatOpenAI
+ model: gpt-4o
+ api_key: $OPENAI_API_KEY
+ request_timeout: 600.0
+ max_retries: 2
+ supports_vision: true
+
+ - name: my-fast-model
+ use: langchain_openai:ChatOpenAI
+ model: gpt-4o-mini
+ api_key: $OPENAI_API_KEY
+```
+
+第一个条目(`my-primary-model`)成为默认值。任何不指定模型或指定未知模型名称的请求都将使用它。
+
+## 思考模式
+
+如果模型支持扩展思考(例如 DeepSeek Reasoner、启用思考的 Doubao、Anthropic Claude 思考模式),Lead Agent 可以在**思考模式**下运行。在此模式下,模型的内部推理步骤在响应流中可见。
+
+思考模式通过每次请求的 `thinking_enabled` 标志控制。如果启用了思考但配置的模型不支持,系统会优雅地回退并记录警告。
+
+## 计划模式
+
+当请求配置中将 `is_plan_mode` 设为 `true` 时,`TodoMiddleware` 被激活。Agent 然后维护一个结构化的任务列表,在处理复杂任务时将条目标记为 `in_progress`、`completed` 或 `pending`。这为用户提供了 Agent 进度的可见性。
+
+计划模式适用于显示增量进度有价值的复杂、多步骤任务。对于简单请求,最好禁用它以避免不必要的开销。
+
+## 自定义 Agent
+
+相同的 Lead Agent 运行时同时为默认 Agent 和你创建的任何自定义 Agent 提供服务。自定义 Agent 的区别仅在于:
+
+- 其**名称**(ASCII slug,从 `display_name` 自动派生)
+- 其**系统提示**或 Agent 特定指令
+- 它有权访问的**技能**
+- 它可以使用的**工具组**
+- 它默认使用的**模型**
+
+自定义 Agent 通过 DeerFlow 应用界面或 `/api/agents` 端点创建。其配置存储在后端目录的 `agents/{name}/config.yaml` 中。
+
+
+ 当在线程中选择自定义 Agent 时,Lead Agent 在运行时加载该 Agent 的配置。为特定
+ Agent 切换模型或技能不需要重启服务器。
+
+
+
+
+
+
diff --git a/frontend/src/content/zh/harness/mcp.mdx b/frontend/src/content/zh/harness/mcp.mdx
new file mode 100644
index 0000000000..4bf72c1a65
--- /dev/null
+++ b/frontend/src/content/zh/harness/mcp.mdx
@@ -0,0 +1,111 @@
+---
+title: MCP 集成
+description: Model Context Protocol(MCP) 是连接语言模型与外部工具和数据源的开放标准。DeerFlow 的 MCP 集成允许你用任何实现了 MCP 协议的工具服务器扩展 Agent——无需修改 Harness 本身。
+---
+
+import { Callout, Cards, Steps } from "nextra/components";
+
+# MCP 集成
+
+
+ Model Context Protocol(MCP)让 DeerFlow
+ 能够连接任何外部工具服务器。连接后,MCP 工具与内置工具一样对 Lead Agent 可用。
+
+
+**Model Context Protocol(MCP)** 是连接语言模型与外部工具和数据源的开放标准。DeerFlow 的 MCP 集成允许你用任何实现了 MCP 协议的工具服务器扩展 Agent——无需修改 Harness 本身。
+
+## 配置
+
+MCP 服务器在 `extensions_config.json` 中配置,这个文件独立于 `config.yaml`。这种分离允许 MCP 和技能配置独立管理,并在运行时通过 Gateway API 更新。
+
+默认位置是项目根目录(与 `config.yaml` 同一目录)。
+
+```json
+{
+ "mcpServers": {
+ "my-server": {
+ "command": "npx",
+ "args": ["-y", "@my-org/my-mcp-server"],
+ "enabled": true
+ },
+ "filesystem": {
+ "command": "npx",
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"],
+ "enabled": true
+ },
+ "sqlite": {
+ "command": "uvx",
+ "args": ["mcp-server-sqlite", "--db-path", "/path/to/db.sqlite"],
+ "enabled": false
+ }
+ }
+}
+```
+
+每个服务器条目支持:
+
+- `command`:要运行的可执行文件(如 `npx`、`uvx`、`python`)
+- `args`:命令参数数组
+- `enabled`:服务器是否激活(可切换而无需删除条目)
+- `env`:可选地注入到服务器进程中的环境变量
+
+## 工具如何加载
+
+
+
+### 启动初始化
+
+DeerFlow 服务器启动时调用 `initialize_mcp_tools()`。这连接到所有启用的 MCP 服务器,检索其工具 schema,并缓存结果。
+
+### 缓存失效
+
+MCP 工具缓存追踪 `extensions_config.json` 的修改时间(`mtime`)。当文件更改时——例如通过 Gateway API 启用或禁用服务器时——缓存被标记为过时,下次请求时重新加载工具。
+
+这意味着 MCP 服务器更改无需重启 DeerFlow 服务器即可生效。
+
+### 工具可用性
+
+加载后,MCP 工具与内置和社区工具一起出现在 Lead Agent 的工具列表中。Agent 使用与其他工具相同的机制选择和调用它们。
+
+
+
+## 工具搜索集成
+
+当许多 MCP 服务器暴露大量工具时,预先将所有工具加载到 Agent 上下文中会增加 token 使用量并降低工具选择准确性。
+
+启用**工具搜索**改为按需加载 MCP 工具:
+
+```yaml
+# config.yaml
+tool_search:
+ enabled: true
+```
+
+启用工具搜索后,MCP 工具按名称列在系统提示中,但不包含完整的工具 schema。Agent 使用 `tool_search` 内置工具发现它们,只将需要的工具加载到上下文中。
+
+## OAuth 支持
+
+某些 MCP 服务器需要 OAuth 认证。DeerFlow 的 `mcp/oauth.py` 处理声明了 OAuth 需求的服务器的 OAuth 流程。
+
+当连接到受 OAuth 保护的 MCP 服务器时,DeerFlow 会:
+
+1. 从服务器能力头中检测 OAuth 需求
+2. 使用 `get_initial_oauth_headers()` 构建适当的授权头
+3. 通过 `build_oauth_tool_interceptor()` 用 OAuth 拦截器包装工具调用
+
+OAuth 流程对 Lead Agent 是透明的——它只是调用工具,DeerFlow 处理认证。
+
+## 管理 MCP 服务器
+
+MCP 服务器可以通过多种方式管理:
+
+- **通过 DeerFlow 应用界面**:扩展面板显示已连接的 MCP 服务器,允许你启用/禁用它们。
+- **通过 Gateway API**:`POST /api/extensions/mcp/{name}/enable` 和 `/disable`。
+- **直接编辑 `extensions_config.json`**:适用于脚本化或程序化配置。
+
+由于基于文件 mtime 的缓存失效机制,更改会自动被检测到。
+
+
+
+
+
diff --git a/frontend/src/content/zh/harness/memory.mdx b/frontend/src/content/zh/harness/memory.mdx
new file mode 100644
index 0000000000..edf2a0e267
--- /dev/null
+++ b/frontend/src/content/zh/harness/memory.mdx
@@ -0,0 +1,122 @@
+---
+title: 记忆系统
+description: 记忆是 DeerFlow Harness 的一个运行时功能。它不是简单的对话日志,而是跨多个独立会话持久化、在未来对话中影响 Agent 行为的结构化事实和上下文摘要存储。
+---
+
+import { Callout, Cards } from "nextra/components";
+
+# 记忆系统
+
+
+ 记忆让 DeerFlow 在多个会话中保留有用信息。Agent
+ 记住用户偏好、项目背景和反复出现的事实,这样它可以在不每次从零开始的情况下给出更好的响应。
+
+
+记忆是 DeerFlow Harness 的一个运行时功能。它不是简单的对话日志,而是跨多个独立会话持久化、在未来对话中影响 Agent 行为的结构化事实和上下文摘要存储。
+
+## 记忆存储什么
+
+记忆存储包含几类信息:
+
+- **工作上下文**:用户正在进行的项目摘要、目标和反复出现的话题。
+- **个人上下文**:Agent 学到的偏好、沟通风格和其他用户特定细节。
+- **近期关注**:最近的关注领域和活跃任务。
+- **历史**:近几个月的上下文、早期背景和长期事实。
+- **事实**:Agent 从对话中提取的离散具体事实(例如偏好的工具、团队名称、项目约束)。
+
+每个类别随着 Agent 从持续对话中学习而随时间更新。
+
+## 工作原理
+
+记忆由 `MemoryMiddleware` 管理,在每次 Lead Agent 轮次上运行:
+
+1. **注入**:每次对话开始时,Agent 当前记忆以受控的 token 预算(`max_injection_tokens`)注入到系统提示中。
+2. **学习**:对话结束后,后台任务提取新事实并更新相关记忆类别。更新通过 `debounce_seconds` 防抖以批量处理快速变化。
+3. **按 Agent 记忆**:当自定义 Agent 激活时,其记忆独立于全局记忆存储。这保持不同 Agent 的知识隔离。
+
+## 配置
+
+```yaml
+memory:
+ enabled: true
+
+ # 全局记忆文件的存储路径。
+ # 默认:{base_dir}/memory.json(解析为 backend/.deer-flow/memory.json)
+ # 绝对路径按原样使用,相对路径相对于 base_dir 解析。
+ storage_path: memory.json
+
+ # 存储类(默认:基于文件的 JSON 存储)
+ storage_class: deerflow.agents.memory.storage.FileMemoryStorage
+
+ # 处理排队记忆更新前等待的秒数(防抖)
+ debounce_seconds: 30
+
+ # 记忆更新提取使用的模型(null = 使用默认模型)
+ model_name: null
+
+ # 要存储的最大事实数
+ max_facts: 100
+
+ # 存储事实所需的最低置信度分数(0.0–1.0)
+ fact_confidence_threshold: 0.7
+
+ # 是否将记忆注入到系统提示中
+ injection_enabled: true
+
+ # 注入到系统提示的最大 token 数
+ max_injection_tokens: 2000
+```
+
+## 全局记忆与按 Agent 记忆
+
+DeerFlow 支持两级记忆:
+
+- **全局记忆**:存储在 `{base_dir}/memory.json`。在没有特定 Agent 激活或 Agent 没有按 Agent 记忆文件时使用。
+- **按 Agent 记忆**:存储在 `{base_dir}/agents/{agent_name}/memory.json`。当自定义 Agent 激活时使用,使该 Agent 学到的知识保持独立。
+
+`MemoryMiddleware` 根据请求配置中的活跃 `agent_name` 自动选择正确的记忆文件。
+
+用于记忆存储的 Agent 名称通过 `AGENT_NAME_PATTERN` 验证,以确保文件系统安全。
+
+## 存储位置
+
+默认情况下,记忆文件存储在后端基础目录下:
+
+- 基础目录:`backend/.deer-flow/`
+- 全局记忆:`backend/.deer-flow/memory.json`
+- 按 Agent 记忆:`backend/.deer-flow/agents/{agent_name}/memory.json`
+
+你可以用 `storage_path` 字段更改存储路径。相对路径相对于基础目录解析;使用绝对路径可以将记忆存储在自定义位置。
+
+## 自定义存储后端
+
+`storage_class` 字段允许你用自定义实现替换默认的基于文件的存储。任何继承 `MemoryStorage` 并实现 `load()`、`reload()` 和 `save()` 方法的类都可以使用:
+
+```yaml
+memory:
+ storage_class: mypackage.storage.RedisMemoryStorage
+```
+
+如果配置的类无法加载,系统回退到默认的 `FileMemoryStorage` 并记录错误。
+
+## 禁用记忆
+
+完全禁用记忆:
+
+```yaml
+memory:
+ enabled: false
+```
+
+保留记忆存储但阻止注入到系统提示:
+
+```yaml
+memory:
+ enabled: true
+ injection_enabled: false
+```
+
+
+
+
+
diff --git a/frontend/src/content/zh/harness/middlewares.mdx b/frontend/src/content/zh/harness/middlewares.mdx
new file mode 100644
index 0000000000..abdcfb3fec
--- /dev/null
+++ b/frontend/src/content/zh/harness/middlewares.mdx
@@ -0,0 +1,205 @@
+---
+title: 中间件
+description: 每次 Lead Agent 调用 LLM 时,都会先后执行一条**中间件链**。中间件可以读取和修改 Agent 的状态、向系统提示注入内容、拦截工具调用,并对模型输出做出反应。
+---
+
+import { Callout } from "nextra/components";
+
+# 中间件
+
+
+ 中间件包裹 Lead Agent 中的每次 LLM
+ 调用。它们是添加跨领域行为(如记忆、摘要压缩、澄清和 token
+ 追踪)的主要扩展点。
+
+
+每次 Lead Agent 调用 LLM 时,都会先后执行一条**中间件链**。中间件可以读取和修改 Agent 的状态、向系统提示注入内容、拦截工具调用,并对模型输出做出反应。
+
+这种设计使 Agent 核心保持简单稳定,同时允许丰富的可组合行为分层叠加。
+
+## 链的工作方式
+
+中间件链在每次 Agent 调用时根据当前配置和请求参数构建一次。中间件按定义的顺序运行:
+
+1. 运行时中间件(错误处理、线程数据、上传、悬空工具调用修补)
+2. `SummarizationMiddleware` — 上下文压缩(如果启用)
+3. `TodoMiddleware` — 任务列表管理(仅计划模式)
+4. `TokenUsageMiddleware` — token 追踪(如果启用)
+5. `TitleMiddleware` — 自动生成线程标题
+6. `MemoryMiddleware` — 跨会话记忆注入和队列
+7. `ViewImageMiddleware` — 图像细节注入(如果模型支持视觉)
+8. `DeferredToolFilterMiddleware` — 隐藏延迟工具 schema(如果启用工具搜索)
+9. `SubagentLimitMiddleware` — 限制并行子 Agent 调用(如果启用子 Agent)
+10. `LoopDetectionMiddleware` — 打破重复工具调用循环
+11. 自定义中间件(如有)
+12. `ClarificationMiddleware` — 拦截澄清请求(始终最后)
+
+顺序很重要。摘要压缩在早期运行以在其他处理之前减少上下文。澄清总是最后运行,这样它可以在所有其他中间件完成后拦截。
+
+## 中间件参考
+
+### ClarificationMiddleware
+
+拦截澄清工具调用,并将其转换为面向用户的信息请求。当模型决定在继续之前需要询问用户某事时,此中间件会将该请求呈现出来。
+
+**配置**:由 `guardrails.clarification` 设置控制。
+
+---
+
+### LoopDetectionMiddleware
+
+检测 Agent 是否在没有取得进展的情况下重复进行相同的工具调用。检测到循环时,中间件会介入打破循环,防止 Agent 无限消耗轮次。
+
+**配置**:内置,无需用户配置。
+
+---
+
+### MemoryMiddleware
+
+在每次对话开始时读取持久化记忆事实并将其注入到系统提示中。对话结束后,将后台更新排队,以将新信息纳入记忆存储。
+
+**配置**:参见[记忆系统](/docs/harness/memory)页面和 `config.yaml` 中的 `memory:` 部分。
+
+```yaml
+memory:
+ enabled: true
+ injection_enabled: true
+ max_injection_tokens: 2000
+ debounce_seconds: 30
+```
+
+---
+
+### SubagentLimitMiddleware
+
+限制 Agent 在单次轮次中可以进行的并行子 Agent 任务调用数量。这防止 Agent 生成无限数量的并发子 Agent。
+
+**配置**:每次请求配置中的 `subagent_enabled` 和 `max_concurrent_subagents`。
+
+---
+
+### TitleMiddleware
+
+在第一次交互后自动为线程生成标题。标题从用户的第一条消息和 Agent 的响应中派生。
+
+**配置**:`config.yaml` 中的 `title:` 部分。
+
+```yaml
+title:
+ enabled: true
+ max_words: 6
+ max_chars: 60
+ model_name: null # 使用默认模型
+```
+
+---
+
+### TodoMiddleware
+
+当计划模式激活时,维护一个对用户可见的结构化任务列表。Agent 使用 `write_todos` 工具,随着完成复杂目标,将任务标记为 `pending`、`in_progress` 或 `completed`。
+
+**激活**:在请求配置中设置 `is_plan_mode: true` 时自动启用。不需要 `config.yaml` 条目。
+
+---
+
+### TokenUsageMiddleware
+
+追踪每次模型调用的 LLM token 消耗,并以 `info` 级别记录。对于监控成本和了解长时序任务中 token 的使用位置很有帮助。
+
+**配置**:`config.yaml` 中的 `token_usage:` 部分。
+
+```yaml
+token_usage:
+ enabled: false
+```
+
+---
+
+### SummarizationMiddleware
+
+当对话变长时,对旧消息进行摘要以减少上下文大小。摘要被注入回对话,替代原始消息,在不需要完整 token 成本的情况下保留含义。
+
+**配置**:`config.yaml` 中的 `summarization:` 部分。详见下方详细配置。
+
+---
+
+### ViewImageMiddleware
+
+当当前模型支持视觉(`supports_vision: true`)时,此中间件拦截 `view_image` 工具调用,并将图像内容直接注入到模型的上下文中以便分析。
+
+**激活**:当解析的模型具有 `supports_vision: true` 时自动启用。
+
+---
+
+### DeferredToolFilterMiddleware
+
+当工具搜索启用时,此中间件从模型上下文中隐藏延迟工具 schema。工具通过 `tool_search` 工具按需发现,而不是预先全部列出,从而减少上下文使用。
+
+**配置**:`config.yaml` 中的 `tool_search.enabled: true`。
+
+## 摘要压缩配置详解
+
+`SummarizationMiddleware` 是长时序任务中影响最大的中间件之一。完整配置参考如下:
+
+```yaml
+summarization:
+ enabled: true
+
+ # 摘要使用的模型(null = 使用默认模型)
+ # 推荐使用轻量级、经济的模型,如 "gpt-4o-mini"
+ model_name: null
+
+ # 触发条件——满足任意一个条件时运行摘要
+ trigger:
+ - type: tokens # 当上下文超过 N 个 token 时触发
+ value: 15564
+ # - type: messages # 当消息数超过 N 时触发
+ # value: 50
+ # - type: fraction # 当上下文达到模型最大输入的 X% 时触发
+ # value: 0.8
+
+ # 摘要后保留多少最近历史
+ keep:
+ type: messages
+ value: 10 # 保留最近 10 条消息
+ # 或者按 token 保留:
+ # type: tokens
+ # value: 3000
+
+ # 为摘要器准备消息时要裁剪的最大 token 数
+ trim_tokens_to_summarize: 15564
+
+ # 自定义摘要提示词(null = 使用默认 LangChain 提示词)
+ summary_prompt: null
+```
+
+**触发类型**:
+
+- `tokens`:当对话中总 token 数超过 `value` 时触发。
+- `messages`:当消息数超过 `value` 时触发。
+- `fraction`:当上下文达到模型最大输入 token 限制的 `value` 比例时触发。
+
+**保留类型**:
+
+- `messages`:摘要后保留最后 `value` 条消息。
+- `tokens`:保留最近 `value` 个 token 的历史。
+- `fraction`:保留模型最大输入 token 限制的 `value` 比例的最近历史。
+
+## 编写自定义中间件
+
+自定义中间件可以注入到链中用于专业用途。中间件必须实现 `langchain.agents.middleware` 中的 `AgentMiddleware` 接口:
+
+```python
+from langchain.agents.middleware import AgentMiddleware
+
+class MyMiddleware(AgentMiddleware):
+ async def on_start(self, state, config):
+ # 在模型调用前运行
+ return state, config
+
+ async def on_end(self, state, config):
+ # 在模型调用后运行
+ return state, config
+```
+
+自定义中间件在链末尾 `ClarificationMiddleware` 之前注入。
diff --git a/frontend/src/content/zh/harness/quick-start.mdx b/frontend/src/content/zh/harness/quick-start.mdx
new file mode 100644
index 0000000000..3da8c7b3f4
--- /dev/null
+++ b/frontend/src/content/zh/harness/quick-start.mdx
@@ -0,0 +1,113 @@
+---
+title: 快速上手
+description: 学习如何使用 create_deerflow_agent 创建并运行 DeerFlow Agent,从模型初始化到流式响应。
+---
+
+import { Callout, Cards, Steps } from "nextra/components";
+
+# 快速上手
+
+
+ 本指南介绍如何在 Python 中通过 create_deerflow_agent
+ 创建并运行一个 DeerFlow Agent。
+
+
+理解 DeerFlow Harness 的最快方式,是直接在代码里创建一个 Agent。本快速上手指南将带你完成模型初始化、Agent 创建,以及响应流式输出。
+
+## 前置条件
+
+DeerFlow Harness 需要 Python 3.12 或更高版本。该包是 `deerflow` 代码库的一部分,位于 `backend/packages/harness` 下。
+
+如果你从仓库克隆开始:
+
+```bash
+cd backend
+uv sync
+```
+
+你还需要准备一个来自对应 LangChain Provider 包的聊天模型实例。
+
+## 创建第一个 Agent
+
+
+
+### 导入工厂函数与模型类
+
+```python
+from deerflow.agents import create_deerflow_agent
+from langchain_openai import ChatOpenAI
+```
+
+### 创建模型
+
+```python
+model = ChatOpenAI(
+ model="gpt-4o",
+ api_key="YOUR_OPENAI_API_KEY",
+)
+```
+
+### 创建 Agent
+
+```python
+agent = create_deerflow_agent(model)
+```
+
+这会返回一个已经编译好的 LangGraph Agent,并带有 DeerFlow 默认的中间件链。
+
+### 流式获取响应
+
+```python
+for event in agent.stream(
+ {"messages": [{"role": "user", "content": "解释一下 DeerFlow Harness 是什么。"}]},
+ stream_mode=["messages", "values"],
+):
+ print(event)
+```
+
+
+
+## 添加工具或行为
+
+你可以通过传入工具、系统提示词、运行时特性、中间件或 checkpointer 来自定义 Agent。
+
+```python
+from deerflow.agents import RuntimeFeatures, create_deerflow_agent
+
+agent = create_deerflow_agent(
+ model,
+ system_prompt="你是一个简洁的研究助手。",
+ features=RuntimeFeatures(subagent=True, memory=False),
+ plan_mode=True,
+ name="research-agent",
+)
+```
+
+常用参数:
+
+| 参数 | 说明 |
+| ------------------ | -------------------------- |
+| `tools` | 提供给 Agent 的额外工具 |
+| `system_prompt` | 自定义系统提示词 |
+| `features` | 启用或替换内置运行时能力 |
+| `extra_middleware` | 将自定义中间件插入默认链路 |
+| `plan_mode` | 启用 Todo 风格的任务跟踪 |
+| `checkpointer` | 为多轮运行持久化状态 |
+| `name` | Agent 的逻辑名称 |
+
+## 什么时候使用 DeerFlowClient
+
+如果你想直接操作底层的编译后 Agent 图,使用 `create_deerflow_agent()`。
+
+如果你想使用更高层的嵌入式应用接口,则应使用 `DeerFlowClient`,例如:
+
+- 面向线程的对话封装,
+- 模型 / 技能 / 记忆管理 API,
+- 文件上传与 artifacts,
+- 与 Gateway 一致的返回格式。
+
+
+
+
+
+
diff --git a/frontend/src/content/zh/harness/sandbox.mdx b/frontend/src/content/zh/harness/sandbox.mdx
new file mode 100644
index 0000000000..2b01718d67
--- /dev/null
+++ b/frontend/src/content/zh/harness/sandbox.mdx
@@ -0,0 +1,146 @@
+---
+title: 沙箱
+description: 沙箱为 Lead Agent 提供一个受控环境,在其中可以读取文件、写入输出、运行 Shell 命令并生成产出物。没有沙箱,Agent 只能生成文本;有了沙箱,它可以编写和执行代码、处理数据文件、生成图表并构建交付物。
+---
+
+import { Callout, Cards, Tabs } from "nextra/components";
+
+# 沙箱
+
+
+ 沙箱是 Agent 进行文件和命令操作的隔离工作区。它让 DeerFlow
+ 能够采取真实行动,而不仅仅是对话。
+
+
+沙箱为 Lead Agent 提供一个受控环境,在其中可以读取文件、写入输出、运行 Shell 命令并生成产出物。没有沙箱,Agent 只能生成文本;有了沙箱,它可以编写和执行代码、处理数据文件、生成图表并构建交付物。
+
+## 沙箱模式
+
+DeerFlow 支持三种沙箱模式,选择适合你部署的一种:
+
+### LocalSandbox(默认)
+
+命令直接在主机机器的文件系统上运行,没有容器隔离。
+
+- **适合**:受信任的单用户本地开发工作流。
+- **风险**:Agent 可以访问主机文件系统。默认使用 `allow_host_bash: false` 防止任意命令执行。
+
+```yaml
+sandbox:
+ use: deerflow.sandbox.local:LocalSandboxProvider
+ allow_host_bash: false # 默认;仅对完全受信任的工作流设置为 true
+```
+
+### 基于容器的 AIO 沙箱
+
+命令在隔离容器中运行(Linux/Windows 上的 Docker,macOS 上的 Apple Container)。每个沙箱会话获得一个全新的容器环境。
+
+- **适合**:多用户环境、生产部署,或任何需要执行隔离的场景。
+
+```yaml
+sandbox:
+ use: deerflow.community.aio_sandbox:AioSandboxProvider
+
+ # 可选:容器镜像(下方显示默认值)
+ image: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest
+
+ # 可选:最大并发容器数(默认:3,超出时 LRU 淘汰)
+ replicas: 3
+
+ # 可选:空闲超时(秒,默认:600)
+ idle_timeout: 600
+
+ # 可选:自定义挂载
+ mounts:
+ - host_path: /path/on/host
+ container_path: /home/user/shared
+ read_only: false
+```
+
+安装:`cd backend && uv add 'deerflow-harness[aio-sandbox]'`
+
+### Provisioner 管理的沙箱(Kubernetes)
+
+每个沙箱在 Kubernetes 集群中获得一个专用 Pod,由 Provisioner 服务管理。这提供最强的隔离性,适合有多个并发用户的生产环境。
+
+```yaml
+sandbox:
+ use: deerflow.community.aio_sandbox:AioSandboxProvider
+ provisioner_url: http://provisioner:8002
+```
+
+## 路径映射
+
+沙箱使用路径映射来桥接主机文件系统和容器的虚拟文件系统。始终配置两个关键映射:
+
+| 主机路径 | 容器路径 | 访问权限 |
+| ------------------------------------------- | --------------------------------------------- | -------- |
+| `skills/`(来自 `skills.path`) | `/mnt/skills`(来自 `skills.container_path`) | 只读 |
+| `.deer-flow/threads/{thread_id}/user-data/` | `/mnt/user-data/` | 读写 |
+
+技能目录始终以只读方式挂载。线程将其工作数据(上传文件、输出、中间文件)写入 `/mnt/user-data/`。
+
+### 自定义挂载
+
+你可以为本地沙箱使用 `mounts:` 配置添加额外挂载:
+
+```yaml
+sandbox:
+ use: deerflow.sandbox.local:LocalSandboxProvider
+ mounts:
+ - host_path: /home/user/my-project
+ container_path: /mnt/my-project
+ read_only: true
+```
+
+
+ 自定义挂载的 container_path 不能与保留前缀冲突:
+ /mnt/skills、/mnt/acp-workspace 或{" "}
+ /mnt/user-data。
+
+
+## 输出截断
+
+沙箱工具限制输出大小以保持 Agent 上下文可控。这些限制可配置:
+
+```yaml
+sandbox:
+ use: deerflow.sandbox.local:LocalSandboxProvider
+
+ # bash 使用中间截断(头部 + 尾部)
+ bash_output_max_chars: 20000
+
+ # read_file 使用头部截断
+ read_file_output_max_chars: 50000
+
+ # ls 使用头部截断
+ ls_output_max_chars: 20000
+```
+
+设置为 `0` 禁用截断。
+
+## 安全性
+
+### LocalSandbox
+
+`LocalSandbox` 直接在主机上运行命令。默认情况下,`bash` 工具**被禁用**以防止任意主机命令执行。仅对完全受信任的单用户工作流启用它:
+
+```yaml
+sandbox:
+ allow_host_bash: true # 危险:授予 Agent 对你机器的 Shell 访问权限
+```
+
+即使没有 `bash`,Agent 也可以通过专用文件工具读写文件。
+
+### 容器沙箱
+
+基于容器的沙箱提供文件系统和进程隔离。Agent 看不到或修改主机文件系统,除非通过显式挂载。Provisioner 管理模式增加了额外一层:每个线程获得自己的隔离 Pod。
+
+### 审计中间件
+
+`SandboxAuditMiddleware` 在每次 Agent 轮次上运行,记录所有沙箱操作,提供会话期间访问了哪些文件、运行了哪些命令的审计跟踪。
+
+
+
+
+
diff --git a/frontend/src/content/zh/harness/skills.mdx b/frontend/src/content/zh/harness/skills.mdx
new file mode 100644
index 0000000000..9b578da017
--- /dev/null
+++ b/frontend/src/content/zh/harness/skills.mdx
@@ -0,0 +1,159 @@
+---
+title: 技能
+description: 技能不仅仅是提示词。它是一个自包含的能力包,可以包含结构化指令、分步工作流、领域最佳实践、支撑资源和工具配置。技能按需加载——在任务需要时注入内容,否则不影响上下文。
+---
+
+import { Callout, Cards, FileTree, Steps } from "nextra/components";
+
+# 技能
+
+
+ 技能是面向任务的能力包,教会 Agent 如何完成特定类型的工作。基础 Agent
+ 保持通用;技能在需要时提供专业化。
+
+
+技能不仅仅是提示词。它是一个自包含的能力包,可以包含结构化指令、分步工作流、领域最佳实践、支撑资源和工具配置。技能按需加载——在任务需要时注入内容,否则不影响上下文。
+
+## 技能包含什么
+
+每个技能位于 `skills/public/`(或用户创建技能的 `skills/custom/`)下自己的子目录中。目录包含一个 `SKILL.md` 文件,定义技能的元数据、指令和工作流。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`SKILL.md` 文件是技能的权威定义,由 `skills/parser.py` 解析以提取技能名称、描述、类别、指令以及任何依赖项或工具需求。
+
+## 内置技能
+
+DeerFlow 内置以下公共技能:
+
+| 技能 | 描述 |
+| ------------------------------ | -------------------------------------------- |
+| `deep-research` | 带来源收集、交叉验证和结构化输出的多步骤研究 |
+| `data-analysis` | 数据探索、统计分析和洞察生成 |
+| `chart-visualization` | 从数据创建图表和可视化 |
+| `ppt-generation` | 演示文稿幻灯片生成 |
+| `image-generation` | AI 图像生成工作流 |
+| `code-documentation` | 自动化代码文档生成 |
+| `newsletter-generation` | 新闻简报内容创作 |
+| `podcast-generation` | 播客脚本和大纲生成 |
+| `academic-paper-review` | 结构化学术论文分析 |
+| `consulting-analysis` | 商业咨询框架和分析 |
+| `systematic-literature-review` | 文献综述方法论和综合 |
+| `github-deep-research` | 仓库和代码深度研究 |
+| `frontend-design` | 前端设计和 UI 工作流 |
+| `web-design-guidelines` | 网页设计标准和审查 |
+| `video-generation` | 视频内容规划和生成 |
+
+## 技能生命周期
+
+
+
+### 发现和加载
+
+`skills/loader.py` 中的 `load_skills()` 扫描配置技能路径下的 `public/` 和 `custom/` 目录。它每次调用都重新读取 `ExtensionsConfig.from_file()`,这意味着通过 Gateway API 启用或禁用技能会立即在运行中的 LangGraph 服务器中生效,无需重启。
+
+### 解析
+
+`parser.py` 读取每个 `SKILL.md` 文件并提取结构化元数据:名称、描述、类别、指令以及任何工具或资源需求。
+
+### 安全扫描
+
+`security_scanner.py` 在技能内容加载到 Agent 上下文之前检查潜在危险模式,防止恶意技能内容被注入。
+
+### 依赖安装
+
+`installer.py` 处理技能声明的任何 Python 或系统依赖项,在技能首次加载时安装到运行时环境中。
+
+### 上下文注入
+
+当 Agent 以特定技能在范围内调用时,技能的指令被注入到系统提示中。Agent 在该对话期间可以访问技能的工作流、最佳实践和领域知识。
+
+
+
+## 配置
+
+技能系统在 `config.yaml` 的 `skills:` 下配置:
+
+```yaml
+skills:
+ # 主机上的技能目录路径。
+ # 默认:相对于后端目录的 ../skills
+ # 取消注释以自定义:
+ # path: /absolute/path/to/custom/skills
+
+ # 在沙箱容器中挂载技能的路径
+ container_path: /mnt/skills
+```
+
+`container_path` 很重要:它告诉 Agent 在沙箱内哪里找到技能文件。Harness 自动将主机技能目录挂载到这个容器路径。
+
+## 启用和禁用技能
+
+技能可用性在 `extensions_config.json` 中跟踪(独立于 `config.yaml`)。你可以管理技能状态:
+
+- **通过 DeerFlow 应用界面**:技能面板允许你切换技能的启用/禁用状态。
+- **通过 Gateway API**:`POST /api/extensions/skills/{name}/enable` 和 `/disable`。
+- **直接编辑 `extensions_config.json`**。
+
+由于 `load_skills()` 每次调用都重新读取扩展配置,更改立即生效——无需重启服务器。
+
+## 按自定义 Agent 限制技能
+
+自定义 Agent 可以被限制为特定技能子集。在 Agent 的配置中(存储在 `agents/{name}/config.yaml`),设置 `skills` 列表:
+
+```yaml
+# agents/my-researcher/config.yaml
+name: my-researcher
+skills:
+ - deep-research
+ - academic-paper-review
+```
+
+- **省略或 null**:Agent 加载所有全局启用的技能。
+- **空列表 `[]`**:Agent 没有技能。
+- **命名列表**:Agent 只加载那些特定技能。
+
+## 技能进化
+
+DeerFlow 包含一个可选的**技能进化**功能,允许 Agent 在 `skills/custom/` 目录中自主创建和改进技能:
+
+```yaml
+skill_evolution:
+ enabled: false # 设为 true 允许 Agent 管理技能创建
+ moderation_model_name: null # 安全扫描模型(null = 使用默认模型)
+```
+
+
+ 只在你信任 Agent
+ 输出的环境中启用技能进化。新创建的技能在加载前会经过安全扫描,但该功能给予
+ Agent 对技能目录的写访问权限。
+
+
+## 编写自定义技能
+
+要创建自定义技能:
+
+1. 在 `skills/custom/your-skill-name/` 下创建新目录
+2. 添加定义技能元数据和指令的 `SKILL.md` 文件
+3. 技能将在下次 `load_skills()` 调用时自动被发现
+
+`SKILL.md` 格式遵循与内置技能相同的结构。使用现有公共技能之一作为预期格式的参考。
+
+
+
+
+
diff --git a/frontend/src/content/zh/harness/subagents.mdx b/frontend/src/content/zh/harness/subagents.mdx
new file mode 100644
index 0000000000..0f99a398a0
--- /dev/null
+++ b/frontend/src/content/zh/harness/subagents.mdx
@@ -0,0 +1,127 @@
+---
+title: 子 Agent
+description: 当一个任务对单个推理线程来说太宽泛,或者部分任务可以并行完成时,Lead Agent 将工作委派给**子 Agent**。子 Agent 是一个独立的 Agent 调用,接收特定任务、执行并返回结果。
+---
+
+import { Callout, Cards } from "nextra/components";
+
+# 子 Agent
+
+
+ 子 Agent 是 Lead Agent
+ 委派子任务的专注执行者。它们以隔离的上下文运行,在处理并行或专业工作的同时保持主对话清晰。
+
+
+当一个任务对单个推理线程来说太宽泛,或者部分任务可以并行完成时,Lead Agent 将工作委派给**子 Agent**。子 Agent 是一个独立的 Agent 调用,接收特定任务、执行并返回结果。
+
+## 为什么子 Agent 很重要
+
+子 Agent 解决了长时序工作流中的两个关键问题:
+
+1. **上下文隔离**:子 Agent 只看到完成其任务所需的信息,而不是整个父对话。这保持了每个 Agent 的工作上下文专注且可控。
+2. **并行性**:多个子 Agent 可以并发运行,允许任务的独立部分(例如同时研究多个话题)并行处理。
+
+## 内置子 Agent
+
+DeerFlow 内置两个子 Agent:
+
+### general-purpose
+
+通用推理和执行 Agent,适合委派需要多步骤推理、网络搜索、文件操作和产出物生成的复杂子任务。
+
+- **默认超时**:900 秒(15 分钟)
+- **默认最大轮次**:160
+
+### bash
+
+专门用于在沙箱内执行命令行任务的子 Agent,适合脚本编写、数据处理、文件转换和环境设置任务。
+
+- **默认超时**:900 秒(15 分钟)
+- **默认最大轮次**:80
+- **可用性**:仅当沙箱的 `bash` 工具可用时才暴露(`allow_host_bash: true` 或配置了容器沙箱)
+
+## 委派流程
+
+Lead Agent 使用内置 `task` 工具将工作委派给子 Agent:
+
+```
+task(
+ agent="general-purpose",
+ task="研究 Acme Corp 的前 5 个竞争对手并总结其定价",
+ context="专注于 B2B SaaS 定价模型"
+)
+```
+
+运行时然后:
+
+1. 从注册表查找子 Agent 配置,应用任何 `config.yaml` 覆盖。
+2. 用子 Agent 自己的提示词和工具创建新的 Agent 调用。
+3. 将子 Agent 运行到完成(或直到超时/最大轮次)。
+4. 将子 Agent 的最终输出作为工具结果返回给 Lead Agent。
+
+## 配置
+
+子 Agent 超时和最大轮次通过 `config.yaml` 中的 `subagents:` 部分控制:
+
+```yaml
+subagents:
+ # 所有子 Agent 的默认超时(秒,默认:900 = 15 分钟)
+ timeout_seconds: 900
+
+ # 可选:覆盖所有子 Agent 的最大轮次
+ # max_turns: 120
+
+ # 可选:按 Agent 覆盖
+ agents:
+ general-purpose:
+ timeout_seconds: 1800 # 复杂任务 30 分钟
+ max_turns: 160
+ bash:
+ timeout_seconds: 300 # 快速命令 5 分钟
+ max_turns: 80
+```
+
+按 Agent 覆盖优先于全局 `timeout_seconds` 和 `max_turns` 设置。
+
+## 并发限制
+
+`SubagentLimitMiddleware` 控制 Lead Agent 在单次轮次中可以并行调用多少个子 Agent,通过每次请求的配置控制:
+
+- `subagent_enabled`:是否为此会话激活子 Agent 委派
+- `max_concurrent_subagents`:单次轮次中最大并行任务调用数(默认:3)
+
+如果 Agent 尝试调用超过限制的子 Agent,中间件会裁剪多余的调用。
+
+## ACP Agent(外部 Agent)
+
+除内置子 Agent 外,DeerFlow 还通过 **Agent Connect Protocol (ACP)** 支持委派给外部 Agent。ACP 允许 DeerFlow 调用作为独立进程运行的 Agent(包括用 ACP 适配器包装的第三方 CLI 工具)。
+
+在 `config.yaml` 中配置 ACP Agent:
+
+```yaml
+acp_agents:
+ claude_code:
+ command: npx
+ args: ["-y", "@zed-industries/claude-agent-acp"]
+ description: 用于实现、重构和调试的 Claude Code
+ model: null
+
+ codex:
+ command: npx
+ args: ["-y", "@zed-industries/codex-acp"]
+ description: 用于仓库任务和代码生成的 Codex CLI
+ model: null
+```
+
+Lead Agent 通过 `invoke_acp_agent` 内置工具调用 ACP Agent。
+
+
+ ACP Agent 作为 DeerFlow 管理的子进程运行,通过 ACP 协议通信。标准 CLI
+ 工具(如原始的 `claude` 或 `codex` 命令)默认不兼容
+ ACP——请使用上面列出的适配器包或兼容的 ACP 封装器。
+
+
+
+
+
+
diff --git a/frontend/src/content/zh/harness/tools.mdx b/frontend/src/content/zh/harness/tools.mdx
new file mode 100644
index 0000000000..954a1ad019
--- /dev/null
+++ b/frontend/src/content/zh/harness/tools.mdx
@@ -0,0 +1,213 @@
+---
+title: 工具
+description: Lead Agent 是一个工具调用 Agent。工具是它与世界交互的方式:搜索网络、读写文件、运行命令、委派任务以及向用户呈现输出。
+---
+
+import { Callout, Cards, Tabs } from "nextra/components";
+
+# 工具
+
+
+ 工具是 Lead Agent 可以采取的行动。DeerFlow 提供内置工具、社区集成、MCP
+ 工具和技能工具——全部通过 config.yaml 控制。
+
+
+Lead Agent 是一个工具调用 Agent。工具是它与世界交互的方式:搜索网络、读写文件、运行命令、委派任务以及向用户呈现输出。
+
+DeerFlow 将工具分为四类:
+
+1. **内置工具** — 核心运行时能力,始终对 Agent 可用
+2. **社区工具** — 与外部搜索、抓取和图像服务的集成
+3. **MCP 工具** — 由外部 Model Context Protocol 服务器提供的工具
+4. **技能工具** — 与特定技能包捆绑的工具
+
+## 内置工具
+
+内置工具是 Harness 的一部分,无需配置即可使用。
+
+### task
+
+将子任务委派给子 Agent。当任务对单个推理线程来说太宽泛,或并行工作有利时,Lead Agent 使用此工具。
+
+```
+task(agent="general-purpose", task="...", context="...")
+```
+
+参见[子 Agent](/docs/harness/subagents)页面了解子 Agent 的配置方式。
+
+---
+
+### present_files
+
+将输出文件作为产出物呈现给用户。Agent 在生成文件(报告、图表、代码等)后调用此工具,将其显示在对话中。
+
+---
+
+### view_image
+
+读取图像文件并将其内容注入到模型的上下文中进行视觉分析。仅当活跃模型具有 `supports_vision: true` 时可用。
+
+---
+
+### clarification
+
+在继续之前向用户提问。当模型认为没有足够信息来行动时,由 `ClarificationMiddleware` 触发。
+
+---
+
+### setup_agent
+
+动态配置当前 Agent 会话。在设置新自定义 Agent 的引导流程中使用。
+
+---
+
+### invoke_acp_agent
+
+使用 [Agent Connect Protocol (ACP)](https://agentconnectprotocol.org/) 调用外部 Agent。需要在 `config.yaml` 中配置 `acp_agents:`。参见[子 Agent](/docs/harness/subagents)页面了解 ACP 配置。
+
+---
+
+### tool_search
+
+按名称或描述搜索工具,并按需将其加载到 Agent 上下文中。仅当 `config.yaml` 中 `tool_search.enabled: true` 时激活。当 MCP 或其他工具集暴露大量工具且你希望减少上下文使用时很有用。
+
+## 沙箱文件工具
+
+以下工具与沙箱文件系统交互,需要配置并激活沙箱。
+
+| 工具 | 描述 |
+| ------------- | ---------------------------------------------------------- |
+| `ls` | 列出目录中的文件 |
+| `read_file` | 读取文件内容 |
+| `glob` | 查找匹配模式的文件 |
+| `grep` | 搜索文件内容 |
+| `write_file` | 向文件写入内容 |
+| `str_replace` | 替换文件中的字符串 |
+| `bash` | 执行 Shell 命令(需要 `allow_host_bash: true` 或容器沙箱) |
+
+在 `config.yaml` 的 `tools:` 下配置:
+
+```yaml
+tools:
+ - use: deerflow.sandbox.tools:ls_tool
+ - use: deerflow.sandbox.tools:read_file_tool
+ - use: deerflow.sandbox.tools:glob_tool
+ - use: deerflow.sandbox.tools:grep_tool
+ - use: deerflow.sandbox.tools:write_file_tool
+ - use: deerflow.sandbox.tools:str_replace_tool
+ - use: deerflow.sandbox.tools:bash_tool
+```
+
+## 社区工具
+
+社区工具将 Agent 连接到外部服务。在 `config.yaml` 的 `tools:` 下使用 `use:` 字段指定实现来配置。
+
+### 网络搜索
+
+
+
+```yaml
+tools:
+ - use: deerflow.community.ddg_search.tools:web_search_tool
+```
+无需 API Key。默认配置,适合开发和通用用途。
+
+
+```yaml
+tools:
+ - use: deerflow.community.tavily.tools:web_search_tool
+ api_key: $TAVILY_API_KEY
+```
+高质量搜索,带结构化结果。需要 [Tavily](https://tavily.com) API Key。
+
+安装:`cd backend && uv add 'deerflow-harness[tavily]'`
+
+
+
+```yaml
+tools:
+ - use: deerflow.community.exa.tools:web_search_tool
+ api_key: $EXA_API_KEY
+```
+带神经检索的语义搜索。需要 [Exa](https://exa.ai) API Key。
+
+安装:`cd backend && uv add 'deerflow-harness[exa]'`
+
+
+
+```yaml
+tools:
+ - use: deerflow.community.firecrawl.tools:web_search_tool
+ api_key: $FIRECRAWL_API_KEY
+```
+Firecrawl 驱动的搜索和爬取。需要 [Firecrawl](https://firecrawl.dev) API Key。
+
+
+
+### 网页内容抓取
+
+
+
+```yaml
+tools:
+ - use: deerflow.community.jina_ai.tools:web_fetch_tool
+ api_key: $JINA_API_KEY # 可选;匿名使用有速率限制
+```
+将网页转换为干净的 Markdown。无 API Key 也可使用,但有更严格的速率限制。
+
+
+```yaml
+tools:
+ - use: deerflow.community.exa.tools:web_fetch_tool
+ api_key: $EXA_API_KEY
+```
+
+
+
+### 图像搜索
+
+```yaml
+tools:
+ - use: deerflow.community.image_search.tools:image_search_tool
+```
+
+## 工具组
+
+工具组允许你将工具组织为命名集合,并限制自定义 Agent 可以访问哪些组:
+
+```yaml
+tool_groups:
+ - name: research
+ tools:
+ - web_search
+ - web_fetch
+ - image_search
+ - name: coding
+ tools:
+ - bash
+ - read_file
+ - write_file
+ - str_replace
+ - glob
+ - grep
+```
+
+自定义 Agent 然后可以在其配置中按名称引用组,将其工具访问限制为仅相关集合。
+
+## 工具搜索(延迟加载)
+
+当有许多工具时(特别是来自多个 MCP 服务器),预先加载所有工具会增加上下文使用量并可能混淆模型。工具搜索功能解决了这个问题:
+
+```yaml
+tool_search:
+ enabled: true
+```
+
+启用后,工具不会直接列在模型上下文中。相反,它们在运行时通过 `tool_search` 内置工具按需发现,Agent 按名称或描述搜索,匹配的工具按需加载到上下文中。
+
+当 MCP 服务器暴露数十个工具时特别有用。
+
+
+
+
+
diff --git a/frontend/src/content/zh/index.mdx b/frontend/src/content/zh/index.mdx
index 08d2d35574..5f2a18deb7 100644
--- a/frontend/src/content/zh/index.mdx
+++ b/frontend/src/content/zh/index.mdx
@@ -1,6 +1,65 @@
---
-title: 概览
+title: DeerFlow 文档
description: 了解 DeerFlow,使用 Harness 构建,并部署应用。
---
-# 概览
+# DeerFlow 文档
+
+DeerFlow 是一个用于构建和运行 Agent 系统的框架。它提供了一个运行时 Harness,可以将 Agent 与记忆、工具、技能、沙箱和子 Agent 组合在一起;同时还提供了一个应用层,将这些能力转化为可用的产品体验。
+
+本文档围绕这两个部分组织:
+
+- **DeerFlow Harness**:用于构建 Agent 系统的核心 SDK 和运行时层。
+- **DeerFlow 应用**:构建在 Harness 之上的参考应用,用于部署、运维和终端用户工作流。
+
+如果你想了解 DeerFlow 的工作原理,从简介开始阅读。如果你想基于核心运行时进行开发,请查阅 Harness 文档。如果你想将 DeerFlow 作为应用部署和使用,请查阅应用文档。
+
+## 从这里开始
+
+### 如果你是 DeerFlow 新手
+
+先从概念概述开始。
+
+- [简介](/docs/introduction)
+- [为什么选择 DeerFlow](/docs/introduction/why-deerflow)
+- [Harness 与应用的区别](/docs/introduction/harness-vs-app)
+
+### 如果你想基于 DeerFlow 进行开发
+
+从 Harness 章节开始。这条路径适合想将 DeerFlow 功能集成到自己系统中,或基于 DeerFlow 运行时构建自定义 Agent 产品的团队。
+
+- [DeerFlow Harness](/docs/harness)
+- [快速上手](/docs/harness/quick-start)
+- [配置](/docs/harness/configuration)
+- [自定义与扩展](/docs/harness/customization)
+
+### 如果你想部署和使用 DeerFlow
+
+从应用章节开始。这条路径适合想将 DeerFlow 作为完整应用运行,并了解如何配置、运维和实际使用的团队。
+
+- [DeerFlow 应用](/docs/application)
+- [快速上手](/docs/application/quick-start)
+- [部署指南](/docs/application/deployment-guide)
+- [工作区使用](/docs/application/workspace-usage)
+
+## 文档结构
+
+### 简介
+
+简介章节帮助你在深入实现细节之前建立正确的思维模型。
+
+### DeerFlow Harness
+
+Harness 章节是技术文档的核心,面向想要构建基于 DeerFlow 系统的开发者。
+
+### DeerFlow 应用
+
+应用章节面向想要将 DeerFlow 作为可用产品部署的团队。
+
+### 教程
+
+教程章节提供实践性的、面向任务的学习内容。
+
+### 参考
+
+参考章节提供详细的查阅资料,包括配置、运行时模式、API 和代码映射。
diff --git a/frontend/src/content/zh/introduction/_meta.ts b/frontend/src/content/zh/introduction/_meta.ts
new file mode 100644
index 0000000000..b6d0ebe606
--- /dev/null
+++ b/frontend/src/content/zh/introduction/_meta.ts
@@ -0,0 +1,15 @@
+import type { MetaRecord } from "nextra";
+
+const meta: MetaRecord = {
+ "why-deerflow": {
+ title: "为什么选择 DeerFlow",
+ },
+ "core-concepts": {
+ title: "核心概念",
+ },
+ "harness-vs-app": {
+ title: "Harness 与应用",
+ },
+};
+
+export default meta;
diff --git a/frontend/src/content/zh/introduction/core-concepts.mdx b/frontend/src/content/zh/introduction/core-concepts.mdx
new file mode 100644
index 0000000000..df3bfe4ad5
--- /dev/null
+++ b/frontend/src/content/zh/introduction/core-concepts.mdx
@@ -0,0 +1,94 @@
+---
+title: 核心概念
+description: 在深入了解 DeerFlow 之前,先建立一些贯穿整个系统的核心概念。这些概念解释了 DeerFlow 的优化目标以及其架构设计的原因。
+---
+
+import { Callout, Cards } from "nextra/components";
+
+# 核心概念
+
+
+ 如果你将 DeerFlow 理解为一个长时序 Agent
+ 的运行时,而不仅仅是聊天界面或工作流图,它将最易于理解。
+
+
+在深入了解 DeerFlow 之前,先建立一些贯穿整个系统的核心概念。这些概念解释了 DeerFlow 的优化目标以及其架构设计的原因。
+
+## Harness
+
+在 DeerFlow 中,**Harness** 是为 Agent 提供所需运行环境的运行时层,让 Agent 能够真正完成工作。
+
+框架通常提供抽象和构建块。Harness 更进一步:它打包了一套有主张的运行时能力,让 Agent 无需每次重建相同基础设施,就能在真实环境中进行规划、行动、使用工具、管理文件和处理长时序任务。
+
+在实践中,DeerFlow 的 Harness 包括:
+
+- 工具访问
+- 技能加载
+- 沙箱执行
+- 记忆
+- 子 Agent 协调
+- 上下文管理
+
+这就是为什么 DeerFlow 既不仅是一个模型封装器,也不仅是一个工作流图——它是一个 Agent 运行环境。
+
+## 长时序 Agent
+
+**长时序 Agent** 是一种在一系列动作中持续有效,而不仅产生单一答案的 Agent。
+
+这类 Agent 可能需要:
+
+1. 制定计划
+2. 反复决定下一步
+3. 多次调用工具
+4. 检查和修改文件
+5. 存储中间结果
+6. 最终返回可用的产出物
+
+重点不只是时间长度,而是跨多个步骤的持续协调。
+
+DeerFlow 专为这类工作设计。其架构假设有用的任务通常需要不止一次工具调用和不止一轮推理。
+
+## 技能(Skill)
+
+**技能**是一个面向任务的能力包,教会 Agent 如何完成某一类工作。
+
+技能不仅仅是一个标签。它通常包括结构化指令、工作流程、最佳实践和支撑资源,可以在相关时加载。这使基础 Agent 保持通用性,同时只在需要时添加专业化行为。
+
+在 DeerFlow 中,深度研究是一项技能。数据分析、内容生成、设计工作流和其他任务类型也可以表示为技能。
+
+这是 DeerFlow 心智模型的重要组成部分:运行时保持通用,技能提供专业化。
+
+## 沙箱(Sandbox)
+
+**沙箱**是 Agent 进行文件和命令操作的隔离执行环境。
+
+DeerFlow 不把 Agent 视为纯文本生成器,而是给它一个工作区,让它可以读取文件、写入输出、运行命令并生成产出物。这使系统在编码、分析和多步骤工作流中更加实用。
+
+隔离性很重要,因为执行应该可控且可重现。沙箱让 DeerFlow 支持真实行动,而不仅仅是对话。
+
+## 子 Agent(Subagent)
+
+**子 Agent** 是处理委派子任务的专注执行者。
+
+当一个任务对单个推理线程来说太宽泛时,DeerFlow 可以将工作拆分为较小的单元并分别运行。子 Agent 有助于并行探索、范围隔离执行,以及减轻主 Agent 的负担。
+
+关键思路是隔离。子 Agent 不需要完整的对话历史或父上下文中的每一个细节——它只需要完成其分配工作所需的信息。
+
+## 上下文工程(Context Engineering)
+
+**上下文工程**是控制 Agent 在何时看到、记住和忽略什么内容的实践,以使其在长期任务中保持有效。
+
+长时序任务会给上下文窗口带来压力。如果所有内容永远内联保存,Agent 会变得越来越慢、越来越嘈杂、越来越不可靠。DeerFlow 通过摘要压缩、为子 Agent 创建范围隔离的上下文、以及将文件系统作为外部工作记忆等技术来解决这一问题。
+
+这是 DeerFlow 中最重要的思想之一。好的 Agent 行为不仅仅依靠更强大的模型——还需要在正确的时间给模型提供正确的工作集。
+
+## 记忆(Memory)
+
+**记忆**允许 Agent 在多个会话中保留有用信息。
+
+DeerFlow 的记忆系统存储用户偏好、项目背景和 Agent 从对话中学到的持久性事实。这些信息在后续会话中注入到系统提示中,让 Agent 无需从零开始即可建立在过往上下文之上。
+
+
+
+
+
diff --git a/frontend/src/content/zh/introduction/harness-vs-app.mdx b/frontend/src/content/zh/introduction/harness-vs-app.mdx
new file mode 100644
index 0000000000..bd31b4bad0
--- /dev/null
+++ b/frontend/src/content/zh/introduction/harness-vs-app.mdx
@@ -0,0 +1,62 @@
+---
+title: Harness 与应用
+description: DeerFlow 有两个紧密相关但服务于不同目的的层次:.
+---
+
+import { Callout, Cards } from "nextra/components";
+
+# Harness 与应用
+
+
+ DeerFlow 应用是构建在 DeerFlow Harness 之上的最佳实践 Super Agent 应用,而
+ DeerFlow Harness 是构建自己 Agent 系统的 Python SDK 和运行时基础。
+
+
+DeerFlow 有两个紧密相关但服务于不同目的的层次:
+
+- **DeerFlow Harness** 是运行时基础层。
+- **DeerFlow 应用** 是构建在该基础之上的最佳实践应用。
+
+理解这一区别,能让其余文档更易于阅读。
+
+## Harness 是运行时层
+
+**DeerFlow Harness** 是用于构建和运行长时序 Agent 的可复用系统。
+
+它提供:长时序任务的规划和执行、工具调用和沙箱执行、技能加载和上下文注入、记忆和跨会话持久化、子 Agent 协调、以及完整的配置和扩展系统。
+
+Harness 是 Python 库和运行时引擎。你可以将它导入到自己的应用中,或者直接使用 DeerFlow 应用,后者已经为你完成了所有集成工作。
+
+## 应用是产品层
+
+**DeerFlow 应用**是一个完整的、可部署的产品,它将 Harness 功能封装成可用的体验:
+
+- 基于浏览器的对话工作区(Next.js 前端)
+- FastAPI Gateway,处理 API 操作
+- nginx 反向代理,统一所有服务
+- LangGraph 服务器,运行 DeerFlow Harness
+
+应用是 Harness 所有功能的参考实现。它展示了如何将运行时能力组装成一个对最终用户和运维团队都易于使用的产品。
+
+## 应该从哪里开始
+
+### 我想将 DeerFlow 集成到自己的系统中
+
+使用 **Harness**。将其作为 Python 库导入,使用 `DeerFlowClient` API 发送消息和流式传输响应,并通过 `config.yaml` 配置行为。
+
+→ 从 [Harness 快速上手](/docs/harness/quick-start) 开始
+
+### 我想运行 DeerFlow 作为对话工作区
+
+使用**应用**。运行 `make dev` 或部署 Docker Compose 配置,然后通过浏览器访问。
+
+→ 从 [应用快速上手](/docs/application/quick-start) 开始
+
+### 我想了解架构原理
+
+先阅读**简介**,然后是 Harness 的[设计理念](/docs/harness/design-principles)。
+
+
+
+
+
diff --git a/frontend/src/content/zh/introduction/why-deerflow.mdx b/frontend/src/content/zh/introduction/why-deerflow.mdx
new file mode 100644
index 0000000000..945d844f88
--- /dev/null
+++ b/frontend/src/content/zh/introduction/why-deerflow.mdx
@@ -0,0 +1,54 @@
+---
+title: 为什么选择 DeerFlow
+description: DeerFlow 的诞生是因为现代 Agent 系统需要的不仅仅是一个聊天循环。一个真正有用的 Agent 必须能够进行长时序规划、将任务拆解为子任务、使用工具、操作文件、安全地运行代码,并在复杂任务中保持足够的上下文连贯性。DeerFlow 正是为提供这样的运行时基础而构建的。
+---
+
+import { Callout, Cards } from "nextra/components";
+
+# 为什么选择 DeerFlow
+
+
+ DeerFlow 起源于深度研究,但逐渐演化为一个通用的长时序 Agent
+ 运行时——支持技能、记忆、工具和协作调度。
+
+
+DeerFlow 的诞生是因为现代 Agent 系统需要的不仅仅是一个聊天循环。一个真正有用的 Agent 必须能够进行长时序规划、将任务拆解为子任务、使用工具、操作文件、安全地运行代码,并在复杂任务中保持足够的上下文连贯性。DeerFlow 正是为提供这样的运行时基础而构建的。
+
+## 从深度研究起步
+
+DeerFlow 的第一个版本围绕一个具体目标设计:生成真正有深度的研究产出,而不是轻量级聊天机器人的概要总结。核心思路是让 AI 系统像研究团队一样工作:制定计划、收集来源、交叉验证发现,并输出有实际深度的结构化结果。
+
+这个定位是有效的,但项目很快揭示了更重要的东西。团队不仅将 DeerFlow 用于研究——他们将其应用于数据分析、报告生成、内部自动化、运营工作流,以及其他同样需要多步骤执行的任务。
+
+共同点是清晰的:有价值的部分不仅仅是研究工作流本身,而是其背后的运行时能力。
+
+## 研究是第一个技能,而非整个系统
+
+这种使用方式的转变带来了一个关键结论:深度研究应该被视为更广泛 Agent 运行时中的一项能力,而不是整个产品的定义。
+
+因此,DeerFlow 从一个以单一研究模式为中心的项目演化为一个通用的长时序 Agent Harness。在这个模型中,研究仍然重要,但它成为众多技能中的一项,而非系统的固定形态。
+
+这就是为什么 DeerFlow 被描述为 **Harness**,而不仅仅是框架或应用。
+
+## 为什么 Harness 很重要
+
+Harness 是一个带有主张的 Agent 运行时。它不只是暴露抽象接口。它打包了 Agent 在真实环境中做有用工作所需的基础设施——工具访问、技能加载、沙箱执行、记忆、子 Agent 协调和上下文管理。
+
+这让开发者可以专注于他们试图解决的问题,而不是重复构建相同的基础设施层。
+
+## DeerFlow 解决的问题
+
+DeerFlow 专注于以下几个具体问题:
+
+**长时序执行**:大多数有用的任务不能在单次推理中完成。DeerFlow 的架构假设工具调用、规划和多轮推理是正常情况,而非特例。
+
+**上下文管理**:随着任务变长,上下文窗口压力成为主要挑战。DeerFlow 通过摘要压缩、范围隔离的子 Agent 上下文和文件系统作为外部记忆来主动管理这一问题。
+
+**技能组合**:不同的工作类型需要不同的方法。DeerFlow 让技能成为可组合的能力包,而不是把所有内容硬编码到主 Agent 逻辑中。
+
+**沙箱执行**:真实任务需要文件操作和命令执行。DeerFlow 提供一个隔离的工作区,让 Agent 可以安全地进行实际操作。
+
+
+
+
+
diff --git a/frontend/src/content/zh/reference/_meta.ts b/frontend/src/content/zh/reference/_meta.ts
new file mode 100644
index 0000000000..5001ebfd84
--- /dev/null
+++ b/frontend/src/content/zh/reference/_meta.ts
@@ -0,0 +1,9 @@
+import type { MetaRecord } from "nextra";
+
+const meta: MetaRecord = {
+ "model-providers": {
+ title: "模型接入",
+ },
+};
+
+export default meta;
diff --git a/frontend/src/content/zh/reference/model-providers/_meta.ts b/frontend/src/content/zh/reference/model-providers/_meta.ts
new file mode 100644
index 0000000000..81fd1b56f6
--- /dev/null
+++ b/frontend/src/content/zh/reference/model-providers/_meta.ts
@@ -0,0 +1,9 @@
+import type { MetaRecord } from "nextra";
+
+const meta: MetaRecord = {
+ ark: {
+ title: "火山方舟",
+ },
+};
+
+export default meta;
diff --git a/frontend/src/content/zh/reference/model-providers/ark.mdx b/frontend/src/content/zh/reference/model-providers/ark.mdx
new file mode 100644
index 0000000000..6f6e4ed123
--- /dev/null
+++ b/frontend/src/content/zh/reference/model-providers/ark.mdx
@@ -0,0 +1,8 @@
+---
+title: 火山方舟
+description: 火山方舟模型接入指南。
+---
+
+# 火山方舟
+
+## Coding Plan
diff --git a/frontend/src/content/zh/reference/model-providers/index.mdx b/frontend/src/content/zh/reference/model-providers/index.mdx
new file mode 100644
index 0000000000..be8f9a4503
--- /dev/null
+++ b/frontend/src/content/zh/reference/model-providers/index.mdx
@@ -0,0 +1,7 @@
+---
+title: 模型厂商服务接入
+description: 已支持模型厂商服务的接入参考文档。
+asIndexPage: true
+---
+
+# 更多模型厂商服务接入
diff --git a/frontend/src/content/zh/tutorials/_meta.ts b/frontend/src/content/zh/tutorials/_meta.ts
new file mode 100644
index 0000000000..f01216bc05
--- /dev/null
+++ b/frontend/src/content/zh/tutorials/_meta.ts
@@ -0,0 +1,21 @@
+import type { MetaRecord } from "nextra";
+
+const meta: MetaRecord = {
+ "first-conversation": {
+ title: "第一次对话",
+ },
+ "create-your-first-harness": {
+ title: "创建你的第一个 Harness",
+ },
+ "use-tools-and-skills": {
+ title: "使用工具和技能",
+ },
+ "work-with-memory": {
+ title: "使用记忆系统",
+ },
+ "deploy-your-own-deerflow": {
+ title: "部署你的 DeerFlow",
+ },
+};
+
+export default meta;
diff --git a/frontend/src/content/zh/tutorials/create-your-first-harness.mdx b/frontend/src/content/zh/tutorials/create-your-first-harness.mdx
new file mode 100644
index 0000000000..622fd65535
--- /dev/null
+++ b/frontend/src/content/zh/tutorials/create-your-first-harness.mdx
@@ -0,0 +1,88 @@
+---
+title: 创建你的第一个 Harness
+description: 本教程介绍如何以编程方式使用 DeerFlow Harness Python SDK——直接在你的 Python 代码中导入和使用 DeerFlow,而不是通过 Web 界面。
+---
+
+# 创建你的第一个 Harness
+
+本教程介绍如何以编程方式使用 DeerFlow Harness Python SDK——直接在你的 Python 代码中导入和使用 DeerFlow,而不是通过 Web 界面。
+
+## 前置条件
+
+- Python 3.12+
+- 已安装 `uv`
+- 已克隆 DeerFlow 仓库
+
+## 安装
+
+```bash
+cd deer-flow/backend
+uv sync
+```
+
+## 创建配置
+
+创建一个最小的 `config.yaml`:
+
+```yaml
+config_version: 6
+
+models:
+ - name: gpt-4o
+ use: langchain_openai:ChatOpenAI
+ model: gpt-4o
+ api_key: $OPENAI_API_KEY
+
+sandbox:
+ use: deerflow.sandbox.local:LocalSandboxProvider
+
+tools:
+ - use: deerflow.community.ddg_search.tools:web_search_tool
+ - use: deerflow.sandbox.tools:read_file_tool
+ - use: deerflow.sandbox.tools:write_file_tool
+```
+
+## 编写代码
+
+创建 `my_agent.py`:
+
+```python
+import asyncio
+import os
+from deerflow.client import DeerFlowClient
+from deerflow.config import load_config
+
+# 设置 API Key
+os.environ["OPENAI_API_KEY"] = "sk-..."
+
+# 加载配置
+load_config()
+
+client = DeerFlowClient()
+
+async def main():
+ async for event in client.astream(
+ thread_id="my-first-thread",
+ message="用 Python 写一个斐波那契数列函数,包含文档字符串",
+ config={
+ "configurable": {
+ "model_name": "gpt-4o",
+ }
+ },
+ ):
+ print(event)
+
+asyncio.run(main())
+```
+
+## 运行
+
+```bash
+cd backend
+uv run python my_agent.py
+```
+
+## 下一步
+
+- [使用工具和技能](/docs/tutorials/use-tools-and-skills)
+- [快速上手](/docs/harness/quick-start)
diff --git a/frontend/src/content/zh/tutorials/deploy-your-own-deerflow.mdx b/frontend/src/content/zh/tutorials/deploy-your-own-deerflow.mdx
new file mode 100644
index 0000000000..897be67ca9
--- /dev/null
+++ b/frontend/src/content/zh/tutorials/deploy-your-own-deerflow.mdx
@@ -0,0 +1,72 @@
+---
+title: 部署你的 DeerFlow
+description: 本教程引导你将 DeerFlow 部署到生产环境,使用 Docker Compose 进行多用户访问。
+---
+
+# 部署你的 DeerFlow
+
+本教程引导你将 DeerFlow 部署到生产环境,使用 Docker Compose 进行多用户访问。
+
+## 前置条件
+
+- 已安装 Docker 和 Docker Compose
+- 服务器或 VM(Linux 推荐)
+- LLM API Key
+
+## 步骤
+
+### 1. 克隆仓库
+
+```bash
+git clone https://github.com/bytedance/deer-flow.git
+cd deer-flow
+```
+
+### 2. 创建配置文件
+
+```bash
+cp config.example.yaml config.yaml
+```
+
+编辑 `config.yaml` 添加你的模型配置。
+
+### 3. 创建环境变量文件
+
+```bash
+cat > .env << EOF
+OPENAI_API_KEY=sk-your-key-here
+DEER_FLOW_ROOT=$(pwd)
+BETTER_AUTH_SECRET=$(openssl rand -base64 32)
+BETTER_AUTH_URL=https://your-domain.com
+EOF
+```
+
+### 4. 启动服务
+
+```bash
+docker compose -f docker/docker-compose-dev.yaml up -d
+```
+
+### 5. 验证部署
+
+```bash
+# 检查所有服务健康状态
+curl http://localhost:2026/api/models
+
+# 查看服务日志
+docker compose -f docker/docker-compose-dev.yaml logs -f
+```
+
+访问 `http://your-server:2026` 开始使用。
+
+## 生产注意事项
+
+- 为 nginx 配置 HTTPS/TLS 证书
+- 将 `BETTER_AUTH_SECRET` 设置为强随机字符串
+- 配置防火墙只允许必要端口
+- 定期备份 `backend/.deer-flow/` 目录
+
+## 下一步
+
+- [部署指南(完整版)](/docs/application/deployment-guide)
+- [运维与排障](/docs/application/operations-and-troubleshooting)
diff --git a/frontend/src/content/zh/tutorials/first-conversation.mdx b/frontend/src/content/zh/tutorials/first-conversation.mdx
new file mode 100644
index 0000000000..7c92eaf5ed
--- /dev/null
+++ b/frontend/src/content/zh/tutorials/first-conversation.mdx
@@ -0,0 +1,50 @@
+---
+title: 第一次对话
+description: 本教程引导你在 DeerFlow 中完成第一次完整的 Agent 对话,从启动应用到与 Agent 进行实质性任务交互。
+---
+
+# 第一次对话
+
+本教程引导你在 DeerFlow 中完成第一次完整的 Agent 对话,从启动应用到与 Agent 进行实质性任务交互。
+
+## 前置条件
+
+- DeerFlow 应用已运行(参见[快速上手](/docs/application/quick-start))
+- 至少在 `config.yaml` 中配置了一个模型
+
+## 步骤
+
+### 1. 打开工作区
+
+在浏览器中访问 [http://localhost:2026](http://localhost:2026),你将看到对话工作区。
+
+### 2. 发送第一条消息
+
+在输入框中输入问题,例如:
+
+```
+研究 2024 年三大最受欢迎的开源大语言模型框架,并对比它们的优缺点。
+```
+
+按回车发送。
+
+### 3. 观察 Agent 的工作过程
+
+你将看到 Agent 进入工作状态:
+
+- 展开**思考步骤**查看它调用了哪些工具
+- 观察网络搜索结果的流入
+- 等待最终报告生成
+
+### 4. 与结果互动
+
+报告生成后,你可以:
+
+- 要求对某部分进行详细说明
+- 要求将报告导出为文件(Agent 会使用 `present_files` 工具)
+- 要求基于研究结果创建图表
+
+## 下一步
+
+- [使用工具和技能](/docs/tutorials/use-tools-and-skills)
+- [工作区使用](/docs/application/workspace-usage)
diff --git a/frontend/src/content/zh/tutorials/use-tools-and-skills.mdx b/frontend/src/content/zh/tutorials/use-tools-and-skills.mdx
new file mode 100644
index 0000000000..eedc953088
--- /dev/null
+++ b/frontend/src/content/zh/tutorials/use-tools-and-skills.mdx
@@ -0,0 +1,52 @@
+---
+title: 使用工具和技能
+description: 本教程介绍如何在 DeerFlow 中配置和使用工具(Tools)与技能(Skills),让 Agent 能够访问搜索、文件操作和特定领域能力。
+---
+
+# 使用工具和技能
+
+本教程介绍如何在 DeerFlow 中配置和使用工具(Tools)与技能(Skills),让 Agent 能够访问搜索、文件操作和特定领域能力。
+
+## 配置工具
+
+在 `config.yaml` 中添加工具:
+
+```yaml
+tools:
+ # 网络搜索
+ - use: deerflow.community.ddg_search.tools:web_search_tool
+
+ # 网页内容抓取
+ - use: deerflow.community.jina_ai.tools:web_fetch_tool
+
+ # 沙箱文件操作
+ - use: deerflow.sandbox.tools:ls_tool
+ - use: deerflow.sandbox.tools:read_file_tool
+ - use: deerflow.sandbox.tools:write_file_tool
+ - use: deerflow.sandbox.tools:bash_tool
+```
+
+## 启用技能
+
+通过 DeerFlow 应用界面的技能面板启用技能,或直接编辑 `extensions_config.json`。
+
+示例:启用深度研究技能
+
+1. 打开 DeerFlow 应用
+2. 点击侧边栏中的扩展/技能图标
+3. 找到 `deep-research`,切换为启用
+
+## 使用技能进行研究
+
+启用 `deep-research` 技能后,在对话中选择该技能,然后发送研究请求:
+
+```
+对量子计算的最新进展进行深度研究,重点关注实用化应用前景
+```
+
+Agent 将执行多步骤研究工作流,包括搜索、信息汇总和报告生成。
+
+## 下一步
+
+- [使用记忆系统](/docs/tutorials/work-with-memory)
+- [技能文档](/docs/harness/skills)
diff --git a/frontend/src/content/zh/tutorials/work-with-memory.mdx b/frontend/src/content/zh/tutorials/work-with-memory.mdx
new file mode 100644
index 0000000000..ac3667af8e
--- /dev/null
+++ b/frontend/src/content/zh/tutorials/work-with-memory.mdx
@@ -0,0 +1,59 @@
+---
+title: 使用记忆系统
+description: 本教程介绍如何在 DeerFlow 中启用和使用记忆系统,让 Agent 在多次会话中记住关于你的重要信息。
+---
+
+# 使用记忆系统
+
+本教程介绍如何在 DeerFlow 中启用和使用记忆系统,让 Agent 在多次会话中记住关于你的重要信息。
+
+## 启用记忆
+
+在 `config.yaml` 中启用记忆:
+
+```yaml
+memory:
+ enabled: true
+ injection_enabled: true
+ max_injection_tokens: 2000
+ debounce_seconds: 30
+```
+
+## 记忆的工作方式
+
+记忆通过 `MemoryMiddleware` 自动工作:
+
+1. **第一次对话**:告诉 Agent 关于你的偏好、项目或背景。
+2. **自动学习**:Agent 在后台提取并保存重要事实。
+3. **后续对话**:记忆事实自动注入到系统提示中,Agent 无需你重新解释背景。
+
+## 示例
+
+**第一次对话**:
+
+```
+我是一名 Python 后端开发者,主要使用 FastAPI 和 PostgreSQL。
+我的团队遵循 PEP 8 代码规范,偏好类型注解。
+请记住这些信息,在以后的代码建议中遵循这些规范。
+```
+
+**后续对话**(无需重复背景):
+
+```
+帮我写一个用户认证模块
+```
+
+Agent 会自动生成符合 FastAPI 风格、带类型注解的代码。
+
+## 查看记忆
+
+记忆存储在 `backend/.deer-flow/memory.json`,你可以直接查看和编辑:
+
+```bash
+cat backend/.deer-flow/memory.json
+```
+
+## 下一步
+
+- [部署你的 DeerFlow](/docs/tutorials/deploy-your-own-deerflow)
+- [记忆系统文档](/docs/harness/memory)
diff --git a/frontend/src/core/agents/api.ts b/frontend/src/core/agents/api.ts
index 927b5f20b4..062a14b242 100644
--- a/frontend/src/core/agents/api.ts
+++ b/frontend/src/core/agents/api.ts
@@ -1,3 +1,4 @@
+import { fetch } from "@/core/api/fetcher";
import { getBackendBaseURL } from "@/core/config";
import type { Agent, CreateAgentRequest, UpdateAgentRequest } from "./types";
diff --git a/frontend/src/core/api/api-client.ts b/frontend/src/core/api/api-client.ts
index b862515138..0b4532ca92 100644
--- a/frontend/src/core/api/api-client.ts
+++ b/frontend/src/core/api/api-client.ts
@@ -4,11 +4,39 @@ import { Client as LangGraphClient } from "@langchain/langgraph-sdk/client";
import { getLangGraphBaseURL } from "../config";
+import { isStateChangingMethod, readCsrfCookie } from "./fetcher";
import { sanitizeRunStreamOptions } from "./stream-mode";
+/**
+ * SDK ``onRequest`` hook that mints the ``X-CSRF-Token`` header from the
+ * live ``csrf_token`` cookie just before each outbound fetch.
+ *
+ * Reading the cookie per-request (rather than baking it into the SDK's
+ * ``defaultHeaders`` at construction) handles login / logout / password
+ * change cookie rotation transparently. Both the ``/api/langgraph/*`` SDK
+ * path and the direct REST endpoints in ``fetcher.ts:fetchWithAuth``
+ * share :func:`readCsrfCookie` and :const:`STATE_CHANGING_METHODS` so
+ * the contract stays in lockstep.
+ */
+function injectCsrfHeader(_url: URL, init: RequestInit): RequestInit {
+ if (!isStateChangingMethod(init.method ?? "GET")) {
+ return init;
+ }
+ const token = readCsrfCookie();
+ if (!token) return init;
+ const headers = new Headers(init.headers);
+ if (!headers.has("X-CSRF-Token")) {
+ headers.set("X-CSRF-Token", token);
+ }
+ return { ...init, headers };
+}
+
function createCompatibleClient(isMock?: boolean): LangGraphClient {
+ const apiUrl = getLangGraphBaseURL(isMock);
+ console.log(`Creating API client with base URL: ${apiUrl}`);
const client = new LangGraphClient({
- apiUrl: getLangGraphBaseURL(isMock),
+ apiUrl,
+ onRequest: injectCsrfHeader,
});
const originalRunStream = client.runs.stream.bind(client.runs);
diff --git a/frontend/src/core/api/feedback.ts b/frontend/src/core/api/feedback.ts
new file mode 100644
index 0000000000..bc6021d957
--- /dev/null
+++ b/frontend/src/core/api/feedback.ts
@@ -0,0 +1,42 @@
+import { getBackendBaseURL } from "../config";
+
+import { fetch } from "./fetcher";
+
+export interface FeedbackData {
+ feedback_id: string;
+ rating: number;
+ comment: string | null;
+}
+
+export async function upsertFeedback(
+ threadId: string,
+ runId: string,
+ rating: number,
+ comment?: string,
+): Promise
{
+ const res = await fetch(
+ `${getBackendBaseURL()}/api/threads/${encodeURIComponent(threadId)}/runs/${encodeURIComponent(runId)}/feedback`,
+ {
+ method: "PUT",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ rating, comment: comment ?? null }),
+ },
+ );
+ if (!res.ok) {
+ throw new Error(`Failed to submit feedback: ${res.status}`);
+ }
+ return res.json();
+}
+
+export async function deleteFeedback(
+ threadId: string,
+ runId: string,
+): Promise {
+ const res = await fetch(
+ `${getBackendBaseURL()}/api/threads/${encodeURIComponent(threadId)}/runs/${encodeURIComponent(runId)}/feedback`,
+ { method: "DELETE" },
+ );
+ if (!res.ok && res.status !== 404) {
+ throw new Error(`Failed to delete feedback: ${res.status}`);
+ }
+}
diff --git a/frontend/src/core/api/fetcher.ts b/frontend/src/core/api/fetcher.ts
new file mode 100644
index 0000000000..ca13f425ec
--- /dev/null
+++ b/frontend/src/core/api/fetcher.ts
@@ -0,0 +1,104 @@
+import { buildLoginUrl } from "@/core/auth/types";
+
+/** HTTP methods that the gateway's CSRFMiddleware checks. */
+export type StateChangingMethod = "POST" | "PUT" | "DELETE" | "PATCH";
+
+export const STATE_CHANGING_METHODS: ReadonlySet = new Set(
+ ["POST", "PUT", "DELETE", "PATCH"],
+);
+
+/** Mirror of the gateway's ``should_check_csrf`` decision. */
+export function isStateChangingMethod(method: string): boolean {
+ return (STATE_CHANGING_METHODS as ReadonlySet).has(
+ method.toUpperCase(),
+ );
+}
+
+const CSRF_COOKIE_PREFIX = "csrf_token=";
+
+/**
+ * Read the ``csrf_token`` cookie set by the gateway at login.
+ *
+ * SSR-safe: returns ``null`` when ``document`` is undefined so the same
+ * helper can be imported from server components without a guard.
+ *
+ * Uses `String.split` instead of a regex to side-step ESLint's
+ * `prefer-regexp-exec` rule and the cookie value's reliable `; `
+ * separator (set by the gateway, not the browser, so format is stable).
+ */
+export function readCsrfCookie(): string | null {
+ if (typeof document === "undefined") return null;
+ for (const pair of document.cookie.split("; ")) {
+ if (pair.startsWith(CSRF_COOKIE_PREFIX)) {
+ return decodeURIComponent(pair.slice(CSRF_COOKIE_PREFIX.length));
+ }
+ }
+ return null;
+}
+
+/**
+ * Fetch with credentials and automatic CSRF protection.
+ *
+ * Two centralized contracts every API call needs:
+ *
+ * 1. ``credentials: "include"`` so the HttpOnly access_token cookie
+ * accompanies cross-origin SSR-routed requests.
+ * 2. ``X-CSRF-Token`` header on state-changing methods (POST/PUT/
+ * DELETE/PATCH), echoed from the ``csrf_token`` cookie. The gateway's
+ * CSRFMiddleware enforces Double Submit Cookie comparison and returns
+ * 403 if the header is missing — silently breaking every call site
+ * that uses raw ``fetch()`` instead of this wrapper.
+ *
+ * Auto-redirects to ``/login`` on 401. Caller-supplied headers are
+ * preserved; the helper only ADDS the CSRF header when it isn't already
+ * present, so explicit overrides win.
+ */
+export async function fetch(
+ input: RequestInfo | string,
+ init?: RequestInit,
+): Promise {
+ const url = typeof input === "string" ? input : input.url;
+
+ // Inject CSRF for state-changing methods. GET/HEAD/OPTIONS/TRACE skip
+ // it to mirror the gateway's ``should_check_csrf`` logic exactly.
+ let headers = init?.headers;
+ if (isStateChangingMethod(init?.method ?? "GET")) {
+ const token = readCsrfCookie();
+ if (token) {
+ // Fresh Headers instance so we don't mutate caller-supplied objects.
+ const merged = new Headers(headers);
+ if (!merged.has("X-CSRF-Token")) {
+ merged.set("X-CSRF-Token", token);
+ }
+ headers = merged;
+ }
+ }
+
+ const res = await globalThis.fetch(url, {
+ ...init,
+ headers,
+ credentials: "include",
+ });
+
+ if (res.status === 401) {
+ window.location.href = buildLoginUrl(window.location.pathname);
+ throw new Error("Unauthorized");
+ }
+
+ return res;
+}
+
+/**
+ * Build headers for CSRF-protected requests.
+ *
+ * **Prefer :func:`fetchWithAuth`** for new code — it injects the header
+ * automatically on state-changing methods. This helper exists for legacy
+ * call sites that need to compose headers manually (e.g. inside
+ * `next/server` route handlers that build their own ``Headers`` object).
+ *
+ * Per RFC-001: Double Submit Cookie pattern.
+ */
+export function getCsrfHeaders(): HeadersInit {
+ const token = readCsrfCookie();
+ return token ? { "X-CSRF-Token": token } : {};
+}
diff --git a/frontend/src/core/auth/AuthProvider.tsx b/frontend/src/core/auth/AuthProvider.tsx
new file mode 100644
index 0000000000..652cc49b8f
--- /dev/null
+++ b/frontend/src/core/auth/AuthProvider.tsx
@@ -0,0 +1,165 @@
+"use client";
+
+import { useRouter, usePathname } from "next/navigation";
+import React, {
+ createContext,
+ useContext,
+ useState,
+ useCallback,
+ useEffect,
+ type ReactNode,
+} from "react";
+
+import { type User, buildLoginUrl } from "./types";
+
+// Re-export for consumers
+export type { User };
+
+/**
+ * Authentication context provided to consuming components
+ */
+interface AuthContextType {
+ user: User | null;
+ isAuthenticated: boolean;
+ isLoading: boolean;
+ logout: () => Promise;
+ refreshUser: () => Promise;
+}
+
+const AuthContext = createContext(undefined);
+
+interface AuthProviderProps {
+ children: ReactNode;
+ initialUser: User | null;
+}
+
+/**
+ * AuthProvider - Unified authentication context for the application
+ *
+ * Per RFC-001:
+ * - Only holds display information (user), never JWT or tokens
+ * - initialUser comes from server-side guard, avoiding client flicker
+ * - Provides logout and refresh capabilities
+ */
+export function AuthProvider({ children, initialUser }: AuthProviderProps) {
+ const [user, setUser] = useState(initialUser);
+ const [isLoading, setIsLoading] = useState(false);
+ const router = useRouter();
+ const pathname = usePathname();
+
+ const isAuthenticated = user !== null;
+
+ /**
+ * Fetch current user from FastAPI
+ * Used when initialUser might be stale (e.g., after tab was inactive)
+ */
+ const refreshUser = useCallback(async () => {
+ try {
+ setIsLoading(true);
+ const res = await fetch("/api/v1/auth/me", {
+ credentials: "include",
+ });
+
+ if (res.ok) {
+ const data = await res.json();
+ setUser(data);
+ } else if (res.status === 401) {
+ // Session expired or invalid
+ setUser(null);
+ // Redirect to login if on a protected route
+ if (pathname?.startsWith("/workspace")) {
+ router.push(buildLoginUrl(pathname));
+ }
+ }
+ } catch (err) {
+ console.error("Failed to refresh user:", err);
+ setUser(null);
+ } finally {
+ setIsLoading(false);
+ }
+ }, [pathname, router]);
+
+ /**
+ * Logout - call FastAPI logout endpoint and clear local state
+ * Per RFC-001: Immediately clear local state, don't wait for server confirmation
+ */
+ const logout = useCallback(async () => {
+ // Immediately clear local state to prevent UI flicker
+ setUser(null);
+
+ try {
+ await fetch("/api/v1/auth/logout", {
+ method: "POST",
+ credentials: "include",
+ });
+ } catch (err) {
+ console.error("Logout request failed:", err);
+ // Still redirect even if logout request fails
+ }
+
+ // Redirect to home page
+ router.push("/");
+ }, [router]);
+
+ /**
+ * Handle visibility change - refresh user when tab becomes visible again.
+ * Throttled to at most once per 60 s to avoid spamming the backend on rapid tab switches.
+ */
+ const lastCheckRef = React.useRef(0);
+
+ useEffect(() => {
+ const handleVisibilityChange = () => {
+ if (document.visibilityState !== "visible" || user === null) return;
+ const now = Date.now();
+ if (now - lastCheckRef.current < 60_000) return;
+ lastCheckRef.current = now;
+ void refreshUser();
+ };
+
+ document.addEventListener("visibilitychange", handleVisibilityChange);
+ return () => {
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
+ };
+ }, [user, refreshUser]);
+
+ const value: AuthContextType = {
+ user,
+ isAuthenticated,
+ isLoading,
+ logout,
+ refreshUser,
+ };
+
+ return {children};
+}
+
+/**
+ * Hook to access authentication context
+ * Throws if used outside AuthProvider - this is intentional for proper usage
+ */
+export function useAuth(): AuthContextType {
+ const context = useContext(AuthContext);
+ if (context === undefined) {
+ throw new Error("useAuth must be used within an AuthProvider");
+ }
+ return context;
+}
+
+/**
+ * Hook to require authentication - redirects to login if not authenticated
+ * Useful for client-side checks in addition to server-side guards
+ */
+export function useRequireAuth(): AuthContextType {
+ const auth = useAuth();
+ const router = useRouter();
+ const pathname = usePathname();
+
+ useEffect(() => {
+ // Only redirect if we're sure user is not authenticated (not just loading)
+ if (!auth.isLoading && !auth.isAuthenticated) {
+ router.push(buildLoginUrl(pathname || "/workspace"));
+ }
+ }, [auth.isAuthenticated, auth.isLoading, router, pathname]);
+
+ return auth;
+}
diff --git a/frontend/src/core/auth/gateway-config.ts b/frontend/src/core/auth/gateway-config.ts
new file mode 100644
index 0000000000..61c6ae8507
--- /dev/null
+++ b/frontend/src/core/auth/gateway-config.ts
@@ -0,0 +1,34 @@
+import { z } from "zod";
+
+const gatewayConfigSchema = z.object({
+ internalGatewayUrl: z.string().url(),
+ trustedOrigins: z.array(z.string()).min(1),
+});
+
+export type GatewayConfig = z.infer;
+
+let _cached: GatewayConfig | null = null;
+
+export function getGatewayConfig(): GatewayConfig {
+ if (_cached) return _cached;
+
+ const isDev = process.env.NODE_ENV === "development";
+
+ const rawUrl = process.env.DEER_FLOW_INTERNAL_GATEWAY_BASE_URL?.trim();
+ const internalGatewayUrl =
+ rawUrl?.replace(/\/+$/, "") ??
+ (isDev ? "http://localhost:8001" : undefined);
+
+ const rawOrigins = process.env.DEER_FLOW_TRUSTED_ORIGINS?.trim();
+ const trustedOrigins = rawOrigins
+ ? rawOrigins
+ .split(",")
+ .map((s) => s.trim())
+ .filter(Boolean)
+ : isDev
+ ? ["http://localhost:3000"]
+ : undefined;
+
+ _cached = gatewayConfigSchema.parse({ internalGatewayUrl, trustedOrigins });
+ return _cached;
+}
diff --git a/frontend/src/core/auth/proxy-policy.ts b/frontend/src/core/auth/proxy-policy.ts
new file mode 100644
index 0000000000..9e6f1f424e
--- /dev/null
+++ b/frontend/src/core/auth/proxy-policy.ts
@@ -0,0 +1,55 @@
+export interface ProxyPolicy {
+ /** Allowed upstream path prefixes */
+ readonly allowedPaths: readonly string[];
+ /** Request headers to strip before forwarding */
+ readonly strippedRequestHeaders: ReadonlySet;
+ /** Response headers to strip before returning */
+ readonly strippedResponseHeaders: ReadonlySet;
+ /** Credential mode: which cookie to forward */
+ readonly credential: { readonly type: "cookie"; readonly name: string };
+ /** Timeout in ms */
+ readonly timeoutMs: number;
+ /** CSRF: required for non-GET/HEAD */
+ readonly csrf: boolean;
+}
+
+export const LANGGRAPH_COMPAT_POLICY: ProxyPolicy = {
+ allowedPaths: [
+ "threads",
+ "runs",
+ "assistants",
+ "store",
+ "models",
+ "mcp",
+ "skills",
+ "memory",
+ ],
+ strippedRequestHeaders: new Set([
+ "host",
+ "connection",
+ "keep-alive",
+ "transfer-encoding",
+ "te",
+ "trailer",
+ "upgrade",
+ "authorization",
+ "x-api-key",
+ "origin",
+ "referer",
+ "proxy-authorization",
+ "proxy-authenticate",
+ ]),
+ strippedResponseHeaders: new Set([
+ "connection",
+ "keep-alive",
+ "transfer-encoding",
+ "te",
+ "trailer",
+ "upgrade",
+ "content-length",
+ "set-cookie",
+ ]),
+ credential: { type: "cookie", name: "access_token" },
+ timeoutMs: 120_000,
+ csrf: true,
+};
diff --git a/frontend/src/core/auth/server.ts b/frontend/src/core/auth/server.ts
new file mode 100644
index 0000000000..6ca3195c46
--- /dev/null
+++ b/frontend/src/core/auth/server.ts
@@ -0,0 +1,96 @@
+import { cookies } from "next/headers";
+
+import { getGatewayConfig } from "./gateway-config";
+import { type AuthResult, userSchema } from "./types";
+
+const SSR_AUTH_TIMEOUT_MS = 5_000;
+
+/**
+ * Fetch the authenticated user from the gateway using the request's cookies.
+ * Returns a tagged AuthResult — callers use exhaustive switch, no try/catch.
+ */
+export async function getServerSideUser(): Promise {
+ if (process.env.DEER_FLOW_AUTH_DISABLED === "1") {
+ return {
+ tag: "authenticated",
+ user: {
+ id: "e2e-user",
+ email: "e2e@test.local",
+ system_role: "admin",
+ needs_setup: false,
+ },
+ };
+ }
+
+ const cookieStore = await cookies();
+ const sessionCookie = cookieStore.get("access_token");
+
+ let internalGatewayUrl: string;
+ try {
+ internalGatewayUrl = getGatewayConfig().internalGatewayUrl;
+ } catch (err) {
+ return { tag: "config_error", message: String(err) };
+ }
+
+ if (!sessionCookie) {
+ // No session — check whether the system has been initialised yet.
+ const setupController = new AbortController();
+ const setupTimeout = setTimeout(
+ () => setupController.abort(),
+ SSR_AUTH_TIMEOUT_MS,
+ );
+ try {
+ const setupRes = await fetch(
+ `${internalGatewayUrl}/api/v1/auth/setup-status`,
+ {
+ cache: "no-store",
+ signal: setupController.signal,
+ },
+ );
+ clearTimeout(setupTimeout);
+ if (setupRes.ok) {
+ const setupData = (await setupRes.json()) as { needs_setup?: boolean };
+ if (setupData.needs_setup) {
+ return { tag: "system_setup_required" };
+ }
+ }
+ } catch {
+ clearTimeout(setupTimeout);
+ // If setup-status is unreachable/times out, fall through to unauthenticated.
+ }
+ return { tag: "unauthenticated" };
+ }
+
+ const controller = new AbortController();
+ const timeout = setTimeout(() => controller.abort(), SSR_AUTH_TIMEOUT_MS);
+
+ try {
+ const res = await fetch(`${internalGatewayUrl}/api/v1/auth/me`, {
+ headers: { Cookie: `access_token=${sessionCookie.value}` },
+ cache: "no-store",
+ signal: controller.signal,
+ });
+ clearTimeout(timeout); // Clear immediately — covers all response branches
+
+ if (res.ok) {
+ const parsed = userSchema.safeParse(await res.json());
+ if (!parsed.success) {
+ console.error("[SSR auth] Malformed /auth/me response:", parsed.error);
+ return { tag: "gateway_unavailable" };
+ }
+ if (parsed.data.needs_setup) {
+ return { tag: "needs_setup", user: parsed.data };
+ }
+ return { tag: "authenticated", user: parsed.data };
+ }
+ if (res.status === 401 || res.status === 403) {
+ return { tag: "unauthenticated" };
+ }
+ console.error(`[SSR auth] /api/v1/auth/me responded ${res.status}`);
+ return { tag: "gateway_unavailable" };
+ } catch (err) {
+ clearTimeout(timeout);
+ console.error("[SSR auth] Failed to reach gateway:", err);
+ return { tag: "gateway_unavailable" };
+ }
+}
diff --git a/frontend/src/core/auth/types.ts b/frontend/src/core/auth/types.ts
new file mode 100644
index 0000000000..fc7d7ed003
--- /dev/null
+++ b/frontend/src/core/auth/types.ts
@@ -0,0 +1,94 @@
+import { z } from "zod";
+
+// ── User schema (single source of truth) ──────────────────────────
+
+export const userSchema = z.object({
+ id: z.string(),
+ email: z.string().email(),
+ system_role: z.enum(["admin", "user"]),
+ needs_setup: z.boolean().optional().default(false),
+});
+
+export type User = z.infer;
+
+// ── SSR auth result (tagged union) ────────────────────────────────
+
+export type AuthResult =
+ | { tag: "authenticated"; user: User }
+ | { tag: "needs_setup"; user: User }
+ | { tag: "system_setup_required" }
+ | { tag: "unauthenticated" }
+ | { tag: "gateway_unavailable" }
+ | { tag: "config_error"; message: string };
+
+export function assertNever(x: never): never {
+ throw new Error(`Unexpected auth result: ${JSON.stringify(x)}`);
+}
+
+export function buildLoginUrl(returnPath: string): string {
+ return `/login?next=${encodeURIComponent(returnPath)}`;
+}
+
+// ── Backend error response parsing ────────────────────────────────
+
+const AUTH_ERROR_CODES = [
+ "invalid_credentials",
+ "token_expired",
+ "token_invalid",
+ "user_not_found",
+ "email_already_exists",
+ "provider_not_found",
+ "not_authenticated",
+ "system_already_initialized",
+] as const;
+
+export type AuthErrorCode = (typeof AUTH_ERROR_CODES)[number];
+
+export interface AuthErrorResponse {
+ code: AuthErrorCode;
+ message: string;
+}
+
+const AuthErrorSchema = z.object({
+ code: z.enum(AUTH_ERROR_CODES),
+ message: z.string(),
+});
+
+const ErrorDetailSchema = z.object({
+ msg: z.string(),
+ type: z.enum(["value_error"]),
+ loc: z.array(z.string()),
+});
+
+export function parseAuthError(data: unknown): AuthErrorResponse {
+ // Try top-level {code, message} first
+ const parsed = AuthErrorSchema.safeParse(data);
+ if (parsed.success) return parsed.data;
+
+ // Unwrap FastAPI's {detail: {code, message}} envelope
+ if (typeof data === "object" && data !== null && "detail" in data) {
+ const detail = (data as Record).detail;
+ const nested = AuthErrorSchema.safeParse(detail);
+ if (nested.success) return nested.data;
+ // Legacy string-detail responses
+ if (typeof detail === "string") {
+ return { code: "invalid_credentials", message: detail };
+ } else if (Array.isArray(detail)) {
+ // Handle list of error details (e.g. from Pydantic validation)
+ const firstDetail = detail[0];
+ if (typeof firstDetail === "object" && firstDetail !== null) {
+ const errorDetail = ErrorDetailSchema.safeParse(firstDetail);
+ if (errorDetail.success) {
+ return { code: "invalid_credentials", message: errorDetail.data.msg };
+ }
+ }
+ } else if (typeof detail === "object" && detail !== null) {
+ const errorDetail = ErrorDetailSchema.safeParse(detail);
+ if (errorDetail.success) {
+ return { code: "invalid_credentials", message: errorDetail.data.msg };
+ }
+ }
+ }
+
+ return { code: "invalid_credentials", message: "Authentication failed" };
+}
diff --git a/frontend/src/core/config/index.ts b/frontend/src/core/config/index.ts
index cb06e00ff2..4aaa7122d5 100644
--- a/frontend/src/core/config/index.ts
+++ b/frontend/src/core/config/index.ts
@@ -19,6 +19,10 @@ export function getBackendBaseURL() {
}
export function getLangGraphBaseURL(isMock?: boolean) {
+ console.log(
+ "env.NEXT_PUBLIC_LANGGRAPH_BASE_URL",
+ env.NEXT_PUBLIC_LANGGRAPH_BASE_URL,
+ );
if (env.NEXT_PUBLIC_LANGGRAPH_BASE_URL) {
return new URL(
env.NEXT_PUBLIC_LANGGRAPH_BASE_URL,
diff --git a/frontend/src/core/i18n/locales/en-US.ts b/frontend/src/core/i18n/locales/en-US.ts
index de94e0c982..6c6bb15253 100644
--- a/frontend/src/core/i18n/locales/en-US.ts
+++ b/frontend/src/core/i18n/locales/en-US.ts
@@ -29,6 +29,7 @@ export const enUS: Translations = {
close: "Close",
more: "More",
search: "Search",
+ loadMore: "Load more",
download: "Download",
thinking: "Thinking",
artifacts: "Artifacts",
@@ -236,6 +237,7 @@ export const enUS: Translations = {
reportIssue: "Report a issue",
contactUs: "Contact us",
about: "About DeerFlow",
+ logout: "Log out",
},
// Conversation
@@ -324,6 +326,7 @@ export const enUS: Translations = {
title: "Settings",
description: "Adjust how DeerFlow looks and behaves for you.",
sections: {
+ account: "Account",
appearance: "Appearance",
memory: "Memory",
tools: "Tools",
diff --git a/frontend/src/core/i18n/locales/types.ts b/frontend/src/core/i18n/locales/types.ts
index a8d99e4c70..a4fa38dcb5 100644
--- a/frontend/src/core/i18n/locales/types.ts
+++ b/frontend/src/core/i18n/locales/types.ts
@@ -18,6 +18,7 @@ export interface Translations {
close: string;
more: string;
search: string;
+ loadMore: string;
download: string;
thinking: string;
artifacts: string;
@@ -168,6 +169,7 @@ export interface Translations {
reportIssue: string;
contactUs: string;
about: string;
+ logout: string;
};
// Conversation
@@ -253,6 +255,7 @@ export interface Translations {
title: string;
description: string;
sections: {
+ account: string;
appearance: string;
memory: string;
tools: string;
diff --git a/frontend/src/core/i18n/locales/zh-CN.ts b/frontend/src/core/i18n/locales/zh-CN.ts
index 600cb8f070..2b2372b97e 100644
--- a/frontend/src/core/i18n/locales/zh-CN.ts
+++ b/frontend/src/core/i18n/locales/zh-CN.ts
@@ -29,6 +29,7 @@ export const zhCN: Translations = {
close: "关闭",
more: "更多",
search: "搜索",
+ loadMore: "加载更多",
download: "下载",
thinking: "思考",
artifacts: "文件",
@@ -224,6 +225,7 @@ export const zhCN: Translations = {
reportIssue: "报告问题",
contactUs: "联系我们",
about: "关于 DeerFlow",
+ logout: "退出登录",
},
// Conversation
@@ -309,6 +311,7 @@ export const zhCN: Translations = {
title: "设置",
description: "根据你的偏好调整 DeerFlow 的界面和行为。",
sections: {
+ account: "账号",
appearance: "外观",
memory: "记忆",
tools: "工具",
diff --git a/frontend/src/core/mcp/api.ts b/frontend/src/core/mcp/api.ts
index 003303238e..284c9fd25b 100644
--- a/frontend/src/core/mcp/api.ts
+++ b/frontend/src/core/mcp/api.ts
@@ -1,3 +1,4 @@
+import { fetch } from "@/core/api/fetcher";
import { getBackendBaseURL } from "@/core/config";
import type { MCPConfig } from "./types";
diff --git a/frontend/src/core/memory/api.ts b/frontend/src/core/memory/api.ts
index 5fcf8e4c09..a68a0bab26 100644
--- a/frontend/src/core/memory/api.ts
+++ b/frontend/src/core/memory/api.ts
@@ -1,3 +1,4 @@
+import { fetch } from "../api/fetcher";
import { getBackendBaseURL } from "../config";
import type {
diff --git a/frontend/src/core/messages/utils.ts b/frontend/src/core/messages/utils.ts
index 5ff14bbb77..3c7d4afdc4 100644
--- a/frontend/src/core/messages/utils.ts
+++ b/frontend/src/core/messages/utils.ts
@@ -328,7 +328,11 @@ export function findToolCallResult(toolCallId: string, messages: Message[]) {
}
export function isHiddenFromUIMessage(message: Message) {
- return message.additional_kwargs?.hide_from_ui === true;
+ return (
+ message.additional_kwargs?.hide_from_ui === true ||
+ message.name === "summary" ||
+ message.name === "loop_warning"
+ );
}
/**
diff --git a/frontend/src/core/skills/api.ts b/frontend/src/core/skills/api.ts
index b6a358f030..2fe334a69b 100644
--- a/frontend/src/core/skills/api.ts
+++ b/frontend/src/core/skills/api.ts
@@ -1,3 +1,4 @@
+import { fetch } from "@/core/api/fetcher";
import { getBackendBaseURL } from "@/core/config";
import type { Skill } from "./type";
diff --git a/frontend/src/core/threads/hooks.ts b/frontend/src/core/threads/hooks.ts
index 9292ac12b9..ef605d832b 100644
--- a/frontend/src/core/threads/hooks.ts
+++ b/frontend/src/core/threads/hooks.ts
@@ -1,4 +1,4 @@
-import type { AIMessage, Message } from "@langchain/langgraph-sdk";
+import type { AIMessage, Message, Run } from "@langchain/langgraph-sdk";
import type { ThreadsClient } from "@langchain/langgraph-sdk/client";
import { useStream } from "@langchain/langgraph-sdk/react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
@@ -8,6 +8,7 @@ import { toast } from "sonner";
import type { PromptInputMessage } from "@/components/ai-elements/prompt-input";
import { getAPIClient } from "../api";
+import { fetch } from "../api/fetcher";
import { getBackendBaseURL } from "../config";
import { useI18n } from "../i18n/hooks";
import type { FileInMessage } from "../messages/utils";
@@ -16,7 +17,7 @@ import { useUpdateSubtask } from "../tasks/context";
import type { UploadedFileInfo } from "../uploads";
import { promptInputFilePartToFile, uploadFiles } from "../uploads";
-import type { AgentThread, AgentThreadState } from "./types";
+import type { AgentThread, AgentThreadState, RunMessage } from "./types";
export type ToolEndEvent = {
name: string;
@@ -27,7 +28,8 @@ export type ThreadStreamOptions = {
threadId?: string | null | undefined;
context: LocalSettings["context"];
isMock?: boolean;
- onStart?: (threadId: string) => void;
+ onSend?: (threadId: string) => void;
+ onStart?: (threadId: string, runId: string) => void;
onFinish?: (state: AgentThreadState) => void;
onToolEnd?: (event: ToolEndEvent) => void;
};
@@ -36,79 +38,41 @@ type SendMessageOptions = {
additionalKwargs?: Record;
};
-function normalizeStoredRunId(runId: string | null): string | null {
- if (!runId) {
- return null;
- }
-
- const trimmed = runId.trim();
- if (!trimmed) {
- return null;
- }
+function mergeMessages(
+ historyMessages: Message[],
+ threadMessages: Message[],
+ optimisticMessages: Message[],
+): Message[] {
+ const threadMessageIds = new Set(
+ threadMessages
+ .map((m) => ("tool_call_id" in m ? m.tool_call_id : m.id))
+ .filter(Boolean),
+ );
- const queryIndex = trimmed.indexOf("?");
- if (queryIndex >= 0) {
- const params = new URLSearchParams(trimmed.slice(queryIndex + 1));
- const queryRunId = params.get("run_id")?.trim();
- if (queryRunId) {
- return queryRunId;
+ // The overlap is a contiguous suffix of historyMessages (newest history == oldest thread).
+ // Scan from the end: shrink cutoff while messages are already in thread, stop as soon as
+ // we hit one that isn't — everything before that point is non-overlapping.
+ let cutoff = historyMessages.length;
+ for (let i = historyMessages.length - 1; i >= 0; i--) {
+ const msg = historyMessages[i];
+ if (!msg) {
+ continue;
}
- }
-
- const pathWithoutQueryOrHash = trimmed.split(/[?#]/, 1)[0]?.trim() ?? "";
- if (!pathWithoutQueryOrHash) {
- return null;
- }
-
- const runsMarker = "/runs/";
- const runsIndex = pathWithoutQueryOrHash.lastIndexOf(runsMarker);
- if (runsIndex >= 0) {
- const runIdAfterMarker = pathWithoutQueryOrHash
- .slice(runsIndex + runsMarker.length)
- .split("/", 1)[0]
- ?.trim();
- if (runIdAfterMarker) {
- return runIdAfterMarker;
+ if (
+ (msg?.id && threadMessageIds.has(msg.id)) ||
+ ("tool_call_id" in msg && threadMessageIds.has(msg.tool_call_id))
+ ) {
+ cutoff = i;
+ } else {
+ break;
}
- return null;
}
- const segments = pathWithoutQueryOrHash
- .split("/")
- .map((segment) => segment.trim())
- .filter(Boolean);
- return segments.at(-1) ?? null;
-}
-
-function getRunMetadataStorage(): {
- getItem(key: `lg:stream:${string}`): string | null;
- setItem(key: `lg:stream:${string}`, value: string): void;
- removeItem(key: `lg:stream:${string}`): void;
-} {
- return {
- getItem(key) {
- const normalized = normalizeStoredRunId(
- window.sessionStorage.getItem(key),
- );
- if (normalized) {
- window.sessionStorage.setItem(key, normalized);
- return normalized;
- }
- window.sessionStorage.removeItem(key);
- return null;
- },
- setItem(key, value) {
- const normalized = normalizeStoredRunId(value);
- if (normalized) {
- window.sessionStorage.setItem(key, normalized);
- return;
- }
- window.sessionStorage.removeItem(key);
- },
- removeItem(key) {
- window.sessionStorage.removeItem(key);
- },
- };
+ return [
+ ...historyMessages.slice(0, cutoff),
+ ...threadMessages,
+ ...optimisticMessages,
+ ];
}
function getStreamErrorMessage(error: unknown): string {
@@ -138,6 +102,7 @@ export function useThreadStream({
threadId,
context,
isMock,
+ onSend,
onStart,
onFinish,
onToolEnd,
@@ -149,17 +114,25 @@ export function useThreadStream({
// and to allow access to the current thread id in onUpdateEvent
const threadIdRef = useRef(threadId ?? null);
const startedRef = useRef(false);
-
const listeners = useRef({
+ onSend,
onStart,
onFinish,
onToolEnd,
});
+ const {
+ messages: history,
+ hasMore: hasMoreHistory,
+ loadMore: loadMoreHistory,
+ loading: isHistoryLoading,
+ appendMessages,
+ } = useThreadHistory(onStreamThreadId ?? "");
+
// Keep listeners ref updated with latest callbacks
useEffect(() => {
- listeners.current = { onStart, onFinish, onToolEnd };
- }, [onStart, onFinish, onToolEnd]);
+ listeners.current = { onSend, onStart, onFinish, onToolEnd };
+ }, [onSend, onStart, onFinish, onToolEnd]);
useEffect(() => {
const normalizedThreadId = threadId ?? null;
@@ -173,45 +146,26 @@ export function useThreadStream({
threadIdRef.current = normalizedThreadId;
}, [threadId]);
- const _handleOnStart = useCallback((id: string) => {
+ const handleStreamStart = useCallback((_threadId: string, _runId: string) => {
+ threadIdRef.current = _threadId;
if (!startedRef.current) {
- listeners.current.onStart?.(id);
+ listeners.current.onStart?.(_threadId, _runId);
startedRef.current = true;
}
+ setOnStreamThreadId(_threadId);
}, []);
- const handleStreamStart = useCallback(
- (_threadId: string) => {
- threadIdRef.current = _threadId;
- _handleOnStart(_threadId);
- },
- [_handleOnStart],
- );
-
const queryClient = useQueryClient();
const updateSubtask = useUpdateSubtask();
- const runMetadataStorageRef = useRef<
- ReturnType | undefined
- >(undefined);
-
- if (
- typeof window !== "undefined" &&
- runMetadataStorageRef.current === undefined
- ) {
- runMetadataStorageRef.current = getRunMetadataStorage();
- }
const thread = useStream({
client: getAPIClient(isMock),
assistantId: "lead_agent",
threadId: onStreamThreadId,
- reconnectOnMount: runMetadataStorageRef.current
- ? () => runMetadataStorageRef.current!
- : false,
+ reconnectOnMount: true,
fetchStateHistory: { limit: 1 },
onCreated(meta) {
- handleStreamStart(meta.thread_id);
- setOnStreamThreadId(meta.thread_id);
+ handleStreamStart(meta.thread_id, meta.run_id);
if (context.agent_name && !isMock) {
void getAPIClient()
.threads.update(meta.thread_id, {
@@ -229,6 +183,34 @@ export function useThreadStream({
}
},
onUpdateEvent(data) {
+ if (data["SummarizationMiddleware.before_model"]) {
+ const _messages = [
+ ...(data["SummarizationMiddleware.before_model"].messages ?? []),
+ ];
+
+ if (_messages.length < 2) {
+ return;
+ }
+ for (const m of _messages) {
+ if (m.name === "summary" && m.type === "human") {
+ summarizedRef.current?.add(m.id ?? "");
+ }
+ }
+ const _lastKeepMessage = _messages[2];
+ const _currentMessages = [...messagesRef.current];
+ const _movedMessages: Message[] = [];
+ for (const m of _currentMessages) {
+ if (m.id !== undefined && m.id === _lastKeepMessage?.id) {
+ break;
+ }
+ if (!summarizedRef.current?.has(m.id ?? "")) {
+ _movedMessages.push(m);
+ }
+ }
+ appendMessages(_movedMessages);
+ messagesRef.current = [];
+ }
+
const updates: Array | null> = Object.values(
data || {},
);
@@ -300,17 +282,18 @@ export function useThreadStream({
const [optimisticMessages, setOptimisticMessages] = useState([]);
const [isUploading, setIsUploading] = useState(false);
const sendInFlightRef = useRef(false);
+ const messagesRef = useRef([]);
+ const summarizedRef = useRef>(null);
// Track message count before sending so we know when server has responded
const prevMsgCountRef = useRef(thread.messages.length);
+ summarizedRef.current ??= new Set();
+
// Reset thread-local pending UI state when switching between threads so
// optimistic messages and in-flight guards do not leak across chat views.
useEffect(() => {
startedRef.current = false;
sendInFlightRef.current = false;
- prevMsgCountRef.current = 0;
- setOptimisticMessages([]);
- setIsUploading(false);
}, [threadId]);
// Clear optimistic when server messages arrive (count increases)
@@ -376,12 +359,7 @@ export function useThreadStream({
}
setOptimisticMessages(newOptimistic);
- // Only fire onStart immediately for an existing persisted thread.
- // Brand-new chats should wait for onCreated(meta.thread_id) so URL sync
- // uses the real server-generated thread id.
- if (threadIdRef.current) {
- _handleOnStart(threadId);
- }
+ listeners.current.onSend?.(threadId);
let uploadedFileInfo: UploadedFileInfo[] = [];
@@ -515,19 +493,106 @@ export function useThreadStream({
sendInFlightRef.current = false;
}
},
- [thread, _handleOnStart, t.uploads.uploadingFiles, context, queryClient],
+ [thread, t.uploads.uploadingFiles, context, queryClient],
+ );
+
+ // Cache the latest thread messages in a ref to compare against incoming history messages for deduplication,
+ // and to allow access to the full message list in onUpdateEvent without causing re-renders.
+ if (thread.messages.length >= messagesRef.current.length) {
+ messagesRef.current = thread.messages;
+ }
+
+ const mergedMessages = mergeMessages(
+ history,
+ thread.messages,
+ optimisticMessages,
);
- // Merge thread with optimistic messages for display
- const mergedThread =
- optimisticMessages.length > 0
- ? ({
- ...thread,
- messages: [...thread.messages, ...optimisticMessages],
- } as typeof thread)
- : thread;
+ // Merge history, live stream, and optimistic messages for display
+ // History messages may overlap with thread.messages; thread.messages take precedence
+ const mergedThread = {
+ ...thread,
+ messages: mergedMessages,
+ } as typeof thread;
- return [mergedThread, sendMessage, isUploading] as const;
+ return {
+ thread: mergedThread,
+ sendMessage,
+ isUploading,
+ isHistoryLoading,
+ hasMoreHistory,
+ loadMoreHistory,
+ } as const;
+}
+
+export function useThreadHistory(threadId: string) {
+ const runs = useThreadRuns(threadId);
+ const threadIdRef = useRef(threadId);
+ const runsRef = useRef(runs.data ?? []);
+ const indexRef = useRef(-1);
+ const loadingRef = useRef(false);
+ const [loading, setLoading] = useState(false);
+ const [messages, setMessages] = useState([]);
+
+ loadingRef.current = loading;
+ const loadMessages = useCallback(async () => {
+ if (runsRef.current.length === 0) {
+ return;
+ }
+ const run = runsRef.current[indexRef.current];
+ if (!run || loadingRef.current) {
+ return;
+ }
+ try {
+ setLoading(true);
+ const result: { data: RunMessage[]; hasMore: boolean } = await fetch(
+ `${getBackendBaseURL()}/api/threads/${encodeURIComponent(threadIdRef.current)}/runs/${encodeURIComponent(run.run_id)}/messages`,
+ {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ credentials: "include",
+ },
+ ).then((res) => {
+ return res.json();
+ });
+ const _messages = result.data
+ .filter((m) => !m.metadata.caller?.startsWith("middleware:"))
+ .map((m) => m.content);
+ setMessages((prev) => [..._messages, ...prev]);
+ indexRef.current -= 1;
+ } catch (err) {
+ console.error(err);
+ } finally {
+ setLoading(false);
+ }
+ }, []);
+ useEffect(() => {
+ threadIdRef.current = threadId;
+ if (runs.data && runs.data.length > 0) {
+ runsRef.current = runs.data ?? [];
+ indexRef.current = runs.data.length - 1;
+ }
+ loadMessages().catch(() => {
+ toast.error("Failed to load thread history.");
+ });
+ }, [threadId, runs.data, loadMessages]);
+
+ const appendMessages = useCallback((_messages: Message[]) => {
+ setMessages((prev) => {
+ return [...prev, ..._messages];
+ });
+ }, []);
+ const hasMore = indexRef.current >= 0 || !runs.data;
+ return {
+ runs: runs.data,
+ messages,
+ loading,
+ appendMessages,
+ hasMore,
+ loadMore: loadMessages,
+ };
}
export function useThreads(
@@ -597,6 +662,33 @@ export function useThreads(
});
}
+export function useThreadRuns(threadId?: string) {
+ const apiClient = getAPIClient();
+ return useQuery({
+ queryKey: ["thread", threadId],
+ queryFn: async () => {
+ if (!threadId) {
+ return [];
+ }
+ const response = await apiClient.runs.list(threadId);
+ return response;
+ },
+ refetchOnWindowFocus: false,
+ });
+}
+
+export function useRunDetail(threadId: string, runId: string) {
+ const apiClient = getAPIClient();
+ return useQuery({
+ queryKey: ["thread", threadId, "run", runId],
+ queryFn: async () => {
+ const response = await apiClient.runs.get(threadId, runId);
+ return response;
+ },
+ refetchOnWindowFocus: false,
+ });
+}
+
export function useDeleteThread() {
const queryClient = useQueryClient();
const apiClient = getAPIClient();
diff --git a/frontend/src/core/threads/types.ts b/frontend/src/core/threads/types.ts
index 50f1dab660..2c0263e53e 100644
--- a/frontend/src/core/threads/types.ts
+++ b/frontend/src/core/threads/types.ts
@@ -22,3 +22,12 @@ export interface AgentThreadContext extends Record {
export interface AgentThread extends Thread {
context?: AgentThreadContext;
}
+
+export interface RunMessage {
+ run_id: string;
+ content: Message;
+ metadata: {
+ caller: string;
+ };
+ created_at: string;
+}
diff --git a/frontend/src/core/uploads/api.ts b/frontend/src/core/uploads/api.ts
index 23d463c2db..0ff01fd683 100644
--- a/frontend/src/core/uploads/api.ts
+++ b/frontend/src/core/uploads/api.ts
@@ -2,6 +2,7 @@
* API functions for file uploads
*/
+import { fetch } from "../api/fetcher";
import { getBackendBaseURL } from "../config";
export interface UploadedFileInfo {
diff --git a/frontend/src/env.js b/frontend/src/env.js
index f00fa7a6c4..ea90cac5d5 100644
--- a/frontend/src/env.js
+++ b/frontend/src/env.js
@@ -7,12 +7,6 @@ export const env = createEnv({
* isn't built with invalid env vars.
*/
server: {
- BETTER_AUTH_SECRET:
- process.env.NODE_ENV === "production"
- ? z.string()
- : z.string().optional(),
- BETTER_AUTH_GITHUB_CLIENT_ID: z.string().optional(),
- BETTER_AUTH_GITHUB_CLIENT_SECRET: z.string().optional(),
GITHUB_OAUTH_TOKEN: z.string().optional(),
NODE_ENV: z
.enum(["development", "test", "production"])
@@ -35,10 +29,6 @@ export const env = createEnv({
* middlewares) or client-side so we need to destruct manually.
*/
runtimeEnv: {
- BETTER_AUTH_SECRET: process.env.BETTER_AUTH_SECRET,
- BETTER_AUTH_GITHUB_CLIENT_ID: process.env.BETTER_AUTH_GITHUB_CLIENT_ID,
- BETTER_AUTH_GITHUB_CLIENT_SECRET:
- process.env.BETTER_AUTH_GITHUB_CLIENT_SECRET,
NODE_ENV: process.env.NODE_ENV,
NEXT_PUBLIC_BACKEND_BASE_URL: process.env.NEXT_PUBLIC_BACKEND_BASE_URL,
diff --git a/frontend/src/server/better-auth/client.ts b/frontend/src/server/better-auth/client.ts
deleted file mode 100644
index 493f849930..0000000000
--- a/frontend/src/server/better-auth/client.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { createAuthClient } from "better-auth/react";
-
-export const authClient = createAuthClient();
-
-export type Session = typeof authClient.$Infer.Session;
diff --git a/frontend/src/server/better-auth/config.ts b/frontend/src/server/better-auth/config.ts
deleted file mode 100644
index abf50facaa..0000000000
--- a/frontend/src/server/better-auth/config.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { betterAuth } from "better-auth";
-
-export const auth = betterAuth({
- emailAndPassword: {
- enabled: true,
- },
-});
-
-export type Session = typeof auth.$Infer.Session;
diff --git a/frontend/src/server/better-auth/index.ts b/frontend/src/server/better-auth/index.ts
deleted file mode 100644
index d705e873e5..0000000000
--- a/frontend/src/server/better-auth/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { auth } from "./config";
diff --git a/frontend/src/server/better-auth/server.ts b/frontend/src/server/better-auth/server.ts
deleted file mode 100644
index 064cd349ce..0000000000
--- a/frontend/src/server/better-auth/server.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { headers } from "next/headers";
-import { cache } from "react";
-
-import { auth } from ".";
-
-export const getSession = cache(async () =>
- auth.api.getSession({ headers: await headers() }),
-);
diff --git a/frontend/tests/e2e/utils/mock-api.ts b/frontend/tests/e2e/utils/mock-api.ts
index ca4862c098..e2d515329e 100644
--- a/frontend/tests/e2e/utils/mock-api.ts
+++ b/frontend/tests/e2e/utils/mock-api.ts
@@ -178,10 +178,51 @@ export function mockLangGraphAPI(page: Page, options?: MockAPIOptions) {
return route.fallback();
});
+ // The URL carries a query string (e.g. `?limit=10&offset=0`), which Playwright
+ // glob `*` does NOT cross, so we match with a regex anchored to `/runs`
+ // followed by `?` or end-of-string. This must NOT match `/runs/stream`.
+ void page.route(/\/api\/langgraph\/threads\/[^/]+\/runs(\?|$)/, (route) => {
+ if (route.request().method() === "GET") {
+ return route.fulfill({
+ status: 200,
+ contentType: "application/json",
+ body: "[]",
+ });
+ }
+ return route.fallback();
+ });
+
// Run stream — returns a minimal SSE response with an AI message
void page.route("**/api/langgraph/runs/stream", handleRunStream);
void page.route("**/api/langgraph/threads/*/runs/stream", handleRunStream);
+ // Models list — model picker dropdown
+ void page.route("**/api/models", (route) => {
+ if (route.request().method() === "GET") {
+ return route.fulfill({
+ status: 200,
+ contentType: "application/json",
+ body: JSON.stringify({
+ models: [],
+ token_usage: { enabled: false },
+ }),
+ });
+ }
+ return route.fallback();
+ });
+
+ // Follow-up suggestions — input box auto-suggest after AI response
+ void page.route("**/api/threads/*/suggestions", (route) => {
+ if (route.request().method() === "POST") {
+ return route.fulfill({
+ status: 200,
+ contentType: "application/json",
+ body: JSON.stringify({ suggestions: [] }),
+ });
+ }
+ return route.fallback();
+ });
+
// Agents list — sidebar & gallery page
void page.route("**/api/agents", (route) => {
if (route.request().method() === "GET") {
diff --git a/scripts/deploy.sh b/scripts/deploy.sh
index 3a8b047bfd..b4b030d4b6 100755
--- a/scripts/deploy.sh
+++ b/scripts/deploy.sh
@@ -3,52 +3,38 @@
# deploy.sh - Build, start, or stop DeerFlow production services
#
# Commands:
-# deploy.sh [--MODE] — build + start (default: --standard)
+# deploy.sh — build + start
# deploy.sh build — build all images (mode-agnostic)
-# deploy.sh start [--MODE] — start from pre-built images (default: --standard)
+# deploy.sh start — start from pre-built images
# 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 + start
# deploy.sh build # build all images
-# deploy.sh start --gateway # start pre-built images in gateway mode
+# deploy.sh start # start pre-built images
# deploy.sh down # stop and remove containers
#
# Must be run from the repo root directory.
set -e
-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
+ echo "Unknown argument: $2"
+ echo "Usage: deploy.sh [build|start|down]"
+ exit 1
fi
;;
- --standard|--gateway)
- CMD=""
- RUNTIME_MODE="${1#--}"
- ;;
"")
CMD=""
;;
*)
echo "Unknown argument: $1"
- echo "Usage: deploy.sh [build|start|down] [--standard|--gateway]"
+ echo "Usage: deploy.sh [build|start|down]"
exit 1
;;
esac
@@ -212,7 +198,7 @@ if [ "$CMD" = "build" ]; then
echo " ✓ Images built successfully"
echo "=========================================="
echo ""
- echo " Next: deploy.sh start [--gateway]"
+ echo " Next: deploy.sh start"
echo ""
exit 0
fi
@@ -225,23 +211,14 @@ echo "=========================================="
echo ""
# ── Detect runtime configuration ────────────────────────────────────────────
-# Only needed for start / up — determines which containers to launch.
+# Only needed for start / up — determines whether provisioner is launched.
sandbox_mode="$(detect_sandbox_mode)"
echo -e "${BLUE}Sandbox mode: $sandbox_mode${NC}"
-echo -e "${BLUE}Runtime mode: $RUNTIME_MODE${NC}"
+echo -e "${BLUE}Runtime: Gateway embedded agent runtime${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
+services="frontend gateway nginx"
if [ "$sandbox_mode" = "provisioner" ]; then
services="$services provisioner"
@@ -282,17 +259,13 @@ fi
echo ""
echo "=========================================="
-echo " DeerFlow is running! ($RUNTIME_MODE mode)"
+echo " DeerFlow is running!"
echo "=========================================="
echo ""
echo " 🌐 Application: http://localhost:${PORT:-2026}"
echo " 📡 API Gateway: http://localhost:${PORT:-2026}/api/*"
-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 " 🤖 Runtime: Gateway embedded"
+echo " API: /api/langgraph/* → Gateway"
echo ""
echo " Manage:"
echo " make down — stop and remove containers"
diff --git a/scripts/docker.sh b/scripts/docker.sh
index b50df15c54..6b37b6b510 100755
--- a/scripts/docker.sh
+++ b/scripts/docker.sh
@@ -148,18 +148,15 @@ 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
+ if [ "$#" -gt 0 ]; then
+ echo -e "${YELLOW}Unknown option for start: $1${NC}"
+ echo "Usage: $0 start"
+ exit 1
+ fi
echo "=========================================="
echo " Starting DeerFlow Docker Development"
@@ -168,21 +165,12 @@ start() {
sandbox_mode="$(detect_sandbox_mode)"
- 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
+ services="frontend gateway nginx"
+ if [ "$sandbox_mode" = "provisioner" ]; then
+ services="frontend gateway provisioner nginx"
fi
- if $gateway_mode; then
- echo -e "${BLUE}Runtime: Gateway mode (experimental) — no LangGraph container${NC}"
- fi
+ echo -e "${BLUE}Runtime: Gateway embedded agent runtime${NC}"
echo -e "${BLUE}Detected sandbox mode: $sandbox_mode${NC}"
if [ "$sandbox_mode" = "provisioner" ]; then
echo -e "${BLUE}Provisioner enabled (Kubernetes mode).${NC}"
@@ -232,12 +220,6 @@ 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 ""
@@ -247,12 +229,8 @@ start() {
echo ""
echo " 🌐 Application: http://localhost:2026"
echo " 📡 API Gateway: http://localhost:2026/api/*"
- if $gateway_mode; then
- echo " 🤖 Runtime: Gateway embedded"
- echo " API: /api/langgraph/* → Gateway (compat)"
- else
- echo " 🤖 LangGraph: http://localhost:2026/api/langgraph/*"
- fi
+ echo " 🤖 Runtime: Gateway embedded"
+ echo " API: /api/langgraph/* → Gateway"
echo ""
echo " 📋 View logs: make docker-logs"
echo " 🛑 Stop: make docker-stop"
@@ -332,7 +310,6 @@ help() {
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 " 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"
diff --git a/scripts/serve.sh b/scripts/serve.sh
index 0d40ebe76d..17d46eede3 100755
--- a/scripts/serve.sh
+++ b/scripts/serve.sh
@@ -3,13 +3,11 @@
# serve.sh — Unified DeerFlow service launcher
#
# Usage:
-# ./scripts/serve.sh [--dev|--prod] [--gateway] [--daemon] [--stop|--restart]
+# ./scripts/serve.sh [--dev|--prod] [--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:
@@ -18,13 +16,11 @@
# --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 --dev # Gateway dev, hot reload
+# ./scripts/serve.sh --prod # Gateway prod
+# ./scripts/serve.sh --dev --daemon # Gateway dev, background
# ./scripts/serve.sh --stop # Stop all services
-# ./scripts/serve.sh --restart --dev --gateway # Restart in gateway mode
+# ./scripts/serve.sh --restart --dev # Restart dev services
#
# Must be run from the repo root directory.
@@ -44,7 +40,6 @@ fi
# ── Argument parsing ─────────────────────────────────────────────────────────
DEV_MODE=true
-GATEWAY_MODE=false
DAEMON_MODE=false
SKIP_INSTALL=false
ACTION="start" # start | stop | restart
@@ -53,14 +48,13 @@ for arg in "$@"; do
case "$arg" in
--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]"
+ echo "Usage: $0 [--dev|--prod] [--daemon] [--skip-install] [--stop|--restart]"
exit 1
;;
esac
@@ -79,7 +73,6 @@ _kill_port() {
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
@@ -88,7 +81,6 @@ stop_all() {
sleep 1
pkill -9 nginx 2>/dev/null || true
# Force-kill any survivors still holding the service ports
- _kill_port 2024
_kill_port 8001
_kill_port 3000
./scripts/cleanup-containers.sh deer-flow-sandbox 2>/dev/null || true
@@ -109,21 +101,11 @@ if [ "$ACTION" = "restart" ]; then
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)"
+if $DEV_MODE; then
+ MODE_LABEL="DEV (Gateway runtime, hot-reload enabled)"
else
- MODE_LABEL="PROD (optimized)"
+ MODE_LABEL="PROD (Gateway runtime, optimized)"
fi
if $DAEMON_MODE; then
@@ -145,8 +127,7 @@ else
FRONTEND_CMD="env BETTER_AUTH_SECRET=$($PYTHON_BIN -c 'import secrets; print(secrets.token_hex(16))') pnpm run preview"
fi
-# Extra flags for uvicorn/langgraph
-LANGGRAPH_EXTRA_FLAGS="--no-reload"
+# Extra flags for uvicorn
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
@@ -185,32 +166,6 @@ 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 ""
@@ -221,10 +176,7 @@ 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 " Gateway → localhost:8001 (REST API + agent runtime)"
echo " Frontend → localhost:3000 (Next.js)"
echo " Nginx → localhost:2026 (reverse proxy)"
echo ""
@@ -268,34 +220,17 @@ run_service() {
mkdir -p logs
mkdir -p temp/client_body_temp temp/proxy_temp temp/fastcgi_temp temp/uwsgi_temp temp/scgi_temp
-# 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}"
- LANGGRAPH_ALLOW_BLOCKING="${LANGGRAPH_ALLOW_BLOCKING:-0}"
- LANGGRAPH_ALLOW_BLOCKING_FLAG=""
- if [ "$LANGGRAPH_ALLOW_BLOCKING" = "1" ]; then
- LANGGRAPH_ALLOW_BLOCKING_FLAG="--allow-blocking"
- fi
- run_service "LangGraph" \
- "cd backend && NO_COLOR=1 CLICOLOR=0 CLICOLOR_FORCE=0 PY_COLORS=0 TERM=dumb 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 2>&1 | LC_ALL=C LC_CTYPE=C LANG=C perl -pe 's/\e\[[0-9;]*[[:alpha:]]//g' > ../logs/langgraph.log" \
- 2024 60
-else
- echo "⏩ Skipping LangGraph (Gateway mode — runtime embedded in Gateway)"
-fi
-
-# 2. Gateway API
+# 1. 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
-# 3. Frontend
+# 2. Frontend
run_service "Frontend" \
"cd frontend && $FRONTEND_CMD > ../logs/frontend.log 2>&1" \
3000 120
-# 4. Nginx
+# 3. 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
@@ -309,16 +244,11 @@ echo "=========================================="
echo ""
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 " Routing: Frontend → Nginx → LangGraph + Gateway"
- echo " API: /api/langgraph/* → LangGraph server (2024)"
-fi
+echo " Routing: Frontend → Nginx → Gateway"
+echo " API: /api/langgraph/* → Gateway agent runtime"
echo " /api/* → Gateway REST API (8001)"
echo ""
-echo " 📋 Logs: logs/{langgraph,gateway,frontend,nginx}.log"
+echo " 📋 Logs: logs/{gateway,frontend,nginx}.log"
echo ""
if $DAEMON_MODE; then