拆解 DeerFlowd:一个开源 Super Agent Harness 是怎么做出来的

如果只看 README,你很容易把 DeerFlow 理解成"一个基于 LangGraph 的开源 Agent UI"。

但真正把源码翻一遍后,会发现它的定位更接近一套完整的 Super Agent Harness

  • 前端不是单纯聊天壳子,而是一个带线程、任务、Artifacts、记忆和设置面板的工作区
  • 后端不是单个服务,而是 Gateway + Harness + Sandbox + Skills + Memory + Subagents 的组合
  • Agent 不是一段固定 prompt,而是由模型、工具、状态、中间件链和配置动态拼装出来的运行时

更关键的是,DeerFlow 解决的不是"模型会不会调工具",而是更接近真实生产环境的问题:

  • 上下文怎么压缩,才不会越聊越乱
  • 旧消息被摘要掉之后,怎么继续沉淀成长期记忆
  • 子代理怎么并发、怎么回传中间态、怎么取消
  • 工具出错了,系统怎么继续跑,而不是直接崩掉
  • 沙箱和文件系统怎么设计,才能既能动手又不失控

总结:

DeerFlow 最值得研究的,不是它功能多,而是它已经长出了一套可以持续演化的 Agent 工程骨架。


一、整体架构:一套 Agent 运行时的系统

从目录结构看,DeerFlow 设计了一个很清楚的分层:

text 复制代码
deer-flow-main/
├─ frontend/              # Next.js 前端
├─ backend/
│  ├─ app/                # FastAPI Gateway 与 IM 渠道
│  └─ packages/harness/   # deerflow-harness,Agent 运行时核心
├─ skills/                # public/custom 技能
├─ docs/
├─ docker/
└─ scripts/

其中最关键的不是"有前后端",而是后端内部又分成了两层:

  • backend/app/:应用层,对外暴露 API、管理文件上传、线程清理、skills/memory 配置等能力
  • backend/packages/harness/deerflow/:框架层,负责 Agent、Sandbox、Subagent、Skills、MCP、Memory、Tracing

这意味着 DeerFlow 的核心不只是某个业务应用,而是一套可复用的 Agent Harness。

如果用部署视角理解,可以把它看成下面这张图:
Browser / Workspace UI
Nginx :2026
Next.js :3000
FastAPI Gateway :8001
LangGraph Runtime :2024
config.yaml / extensions_config.json
deerflow-harness
Model Factory
Skills System
Memory Queue / Storage
Sandbox Provider
Subagent Executor

这个拓扑很像现代应用常见的分工:

  • 前端负责工作区交互
  • Gateway 负责管理类 API
  • LangGraph Runtime 负责线程和运行
  • Harness 负责真正的 Agent 执行逻辑

二、前端:Agent 不只是聊天框,而是一个可视化工作区

DeerFlow 前端基于:

  • Next.js 16
  • React 19
  • App Router
  • TanStack Query
  • @langchain/langgraph-sdk

这里值得注意的是,它没有试图在浏览器里自己做一套 Agent 状态机,而是围绕 LangGraph SDK 做线程流、状态恢复和事件消费。也就是说:

前端负责"工作区体验",运行时负责"Agent 执行语义"。

2.1 useThreadStream():前端与运行时的关键连接点

useThreadStream() 定义在:

  • frontend/src/core/threads/hooks.ts:137-530

这段代码几乎把 DeerFlow 前端的设计哲学写明了:

  • useStream 对接 LangGraph
  • 处理线程创建
  • 监听工具结束事件
  • 监听自定义事件,比如 task_running
  • 做 optimistic messages
  • 处理文件上传和提交流程
  • 在线程切换时清理本地 pending UI 状态

源码节选:

  • frontend/src/core/threads/hooks.ts:204-297
ts 复制代码
const thread = useStream<AgentThreadState>({
  client: getAPIClient(isMock),
  assistantId: "lead_agent",
  threadId: onStreamThreadId,
  reconnectOnMount: runMetadataStorageRef.current
    ? () => runMetadataStorageRef.current!
    : false,
  fetchStateHistory: { limit: 1 },
  onCreated(meta) {
    handleStreamStart(meta.thread_id);
    setOnStreamThreadId(meta.thread_id);
    if (context.agent_name && !isMock) {
      void getAPIClient()
        .threads.update(meta.thread_id, {
          metadata: { agent_name: context.agent_name },
        })
        .catch(() => ({}));
    }
  },
  onLangChainEvent(event) {
    if (event.event === "on_tool_end") {
      listeners.current.onToolEnd?.({
        name: event.name,
        data: event.data,
      });
    }
  },
  onCustomEvent(event: unknown) {
    if (
      typeof event === "object" &&
      event !== null &&
      "type" in event &&
      event.type === "task_running"
    ) {
      const e = event as {
        type: "task_running";
        task_id: string;
        message: AIMessage;
      };
      updateSubtask({ id: e.task_id, latestMessage: e.message });
      return;
    }
  },
});

这段代码的价值不在于"它能连上 SDK",而在于它清楚地表达了前端职责:

  • 线程创建由后端决定,前端只接收真实 thread id
  • 工具事件和子任务事件会被翻译成 UI 状态
  • 前端不自己发明执行逻辑,而是订阅和呈现运行时状态

2.2 发送消息

源码位置:

  • frontend/src/core/threads/hooks.ts:326-530

节选如下:

ts 复制代码
const sendMessage = useCallback(
  async (
    threadId: string,
    message: PromptInputMessage,
    extraContext?: Record<string, unknown>,
  ) => {
    if (sendInFlightRef.current) {
      return;
    }
    sendInFlightRef.current = true;

    prevMsgCountRef.current = thread.messages.length;

    const newOptimistic: Message[] = [];
    if (!hideFromUI) {
      newOptimistic.push({
        type: "human",
        id: `opt-human-${Date.now()}`,
        content: text ? [{ type: "text", text }] : "",
        additional_kwargs: optimisticAdditionalKwargs,
      });
    }
    setOptimisticMessages(newOptimistic);

    await thread.submit(
      {
        messages: [
          {
            type: "human",
            content: [{ type: "text", text }],
            additional_kwargs: {
              ...(filesForSubmit.length > 0 ? { files: filesForSubmit } : {}),
            },
          },
        ],
      },
      {
        threadId,
        streamSubgraphs: true,
        streamResumable: true,
        config: { recursion_limit: 1000 },
        context: {
          ...extraContext,
          ...context,
          thinking_enabled: context.mode !== "flash",
          is_plan_mode: context.mode === "pro" || context.mode === "ultra",
          subagent_enabled: context.mode === "ultra",
          thread_id: threadId,
        },
      },
    );
  },
  [thread, context],
);

这段实现有几个很重要的点:

  • optimistic UI 和真实流式消息是分开的
  • 文件先上传,再把虚拟路径作为 metadata 一起提交
  • 不同模式通过 context 映射成运行时配置,比如 is_plan_modesubagent_enabled

这让前端不是一个"样式层",而是真正参与了 Agent 运行上下文的编排。

2.3 Chat 页面本身也说明它是"工作区"而不是"聊天框"

源码位置:

  • frontend/src/app/workspace/chats/[thread_id]/page.tsx:32-149

节选如下:

tsx 复制代码
export default function ChatPage() {
  const { threadId, setThreadId, isNewThread, setIsNewThread, isMock } =
    useThreadChat();
  const [settings, setSettings] = useThreadSettings(threadId);

  const [thread, sendMessage, isUploading] = useThreadStream({
    threadId: isNewThread ? undefined : threadId,
    context: settings.context,
    isMock,
    onStart: (createdThreadId) => {
      setThreadId(createdThreadId);
      setIsNewThread(false);
      history.replaceState(null, "", `/workspace/chats/${createdThreadId}`);
    },
  });

  return (
    <ThreadContext.Provider value={{ thread, isMock }}>
      <ChatBox threadId={threadId}>
        <ThreadTitle threadId={threadId} thread={thread} />
        <TokenUsageIndicator messages={thread.messages} />
        <ArtifactTrigger />
        <MessageList threadId={threadId} thread={thread} />
        <TodoList todos={thread.values.todos ?? []} />
        <InputBox
          threadId={threadId}
          context={settings.context}
          onContextChange={(context) => setSettings("context", context)}
          onSubmit={(message) => void sendMessage(threadId, message)}
        />
      </ChatBox>
    </ThreadContext.Provider>
  );
}

从这里可以看出,DeerFlow 前端的核心对象已经不只是 message list,而是:

  • 线程
  • 设置上下文
  • token usage
  • artifacts
  • todo
  • 输入框状态

也就是说,用户面对的是一个"Agent 工作台",而不是一块聊天区域。


三、后端:Gateway 负责管理面,Harness 负责运行时

DeerFlow 后端最值得讲的,是它没有把所有逻辑堆在一个服务里,而是分成:

  • backend/app/:应用层
  • backend/packages/harness/deerflow/:运行时框架层

3.1 Gateway:把非推理能力统一收口

入口文件:

  • backend/app/gateway/app.py

Gateway 暴露了这些能力:

  • models
  • mcp
  • memory
  • skills
  • uploads
  • threads
  • artifacts
  • agents
  • suggestions
  • channels
  • runs

这种拆分的意义在于:

  • 前端对管理类能力有统一 API
  • IM 渠道可以复用同样的服务
  • Agent Runtime 可以更聚焦于执行逻辑
  • 后续更容易调整部署模式

3.2 Harness:DeerFlow 真正的核心价值

  • agents/
  • sandbox/
  • subagents/
  • skills/
  • mcp/
  • models/
  • runtime/
  • config/
  • guardrails/
  • tracing/

换句话说,DeerFlow 不是在堆一个 Agent 产品,而是在构建一套 Agent Runtime。


四、Lead Agent:不是写死一个 Agent,而是在造一个 Agent 工厂

主 Agent 入口在:

  • backend/packages/harness/deerflow/agents/lead_agent/agent.py

其中最重要的函数是:

  • make_lead_agent(config)

源码位置:

  • backend/packages/harness/deerflow/agents/lead_agent/agent.py:280-356
python 复制代码
def make_lead_agent(config: RunnableConfig):
    from deerflow.tools import get_available_tools
    from deerflow.tools.builtins import setup_agent

    cfg = config.get("configurable", {})
    thinking_enabled = cfg.get("thinking_enabled", True)
    reasoning_effort = cfg.get("reasoning_effort", None)
    requested_model_name = cfg.get("model_name") or cfg.get("model")
    is_plan_mode = cfg.get("is_plan_mode", False)
    subagent_enabled = cfg.get("subagent_enabled", False)
    max_concurrent_subagents = cfg.get("max_concurrent_subagents", 3)
    is_bootstrap = cfg.get("is_bootstrap", False)
    agent_name = cfg.get("agent_name")

    model_name = _resolve_model_name(requested_model_name or agent_model_name)

    if is_bootstrap:
        return create_agent(
            model=create_chat_model(name=model_name, thinking_enabled=thinking_enabled),
            tools=get_available_tools(model_name=model_name, subagent_enabled=subagent_enabled) + [setup_agent],
            middleware=_build_middlewares(config, model_name=model_name),
            system_prompt=apply_prompt_template(
                subagent_enabled=subagent_enabled,
                max_concurrent_subagents=max_concurrent_subagents,
                available_skills=set(["bootstrap"]),
            ),
            state_schema=ThreadState,
        )

    return create_agent(
        model=create_chat_model(
            name=model_name,
            thinking_enabled=thinking_enabled,
            reasoning_effort=reasoning_effort,
        ),
        tools=get_available_tools(
            model_name=model_name,
            groups=agent_config.tool_groups if agent_config else None,
            subagent_enabled=subagent_enabled,
        ),
        middleware=_build_middlewares(config, model_name=model_name, agent_name=agent_name),
        system_prompt=apply_prompt_template(
            subagent_enabled=subagent_enabled,
            max_concurrent_subagents=max_concurrent_subagents,
            agent_name=agent_name,
            available_skills=set(agent_config.skills) if agent_config and agent_config.skills is not None else None,
        ),
        state_schema=ThreadState,
    )

这段代码说明了三件事:

  1. Agent 不是一个静态对象,而是按运行时参数动态构造出来的
  2. 模型、工具、状态、中间件和系统 prompt 是并列的一组核心要素
  3. bootstrap agent 和默认 lead agent 用的是同一条运行时主链,只是装配不同

这种写法,比"在一个 prompt 文件里写尽所有逻辑"的可扩展性高很多。


五、中间件链:DeerFlow 最有工程味的部分

如果只挑一个最值得学习的设计,我会选中间件链。

5.1 中间件是如何被组装起来的

源码位置:

  • backend/packages/harness/deerflow/agents/lead_agent/agent.py:215-277
python 复制代码
def _build_middlewares(config, model_name, agent_name=None, custom_middlewares=None):
    middlewares = build_lead_runtime_middlewares(lazy_init=True)

    summarization_middleware = _create_summarization_middleware()
    if summarization_middleware is not None:
        middlewares.append(summarization_middleware)

    is_plan_mode = config.get("configurable", {}).get("is_plan_mode", False)
    todo_list_middleware = _create_todo_list_middleware(is_plan_mode)
    if todo_list_middleware is not None:
        middlewares.append(todo_list_middleware)

    if get_app_config().token_usage.enabled:
        middlewares.append(TokenUsageMiddleware())

    middlewares.append(TitleMiddleware())
    middlewares.append(MemoryMiddleware(agent_name=agent_name))

    if model_config is not None and model_config.supports_vision:
        middlewares.append(ViewImageMiddleware())

    if subagent_enabled:
        middlewares.append(SubagentLimitMiddleware(max_concurrent=max_concurrent_subagents))

    middlewares.append(LoopDetectionMiddleware())

    if custom_middlewares:
        middlewares.extend(custom_middlewares)

    middlewares.append(ClarificationMiddleware())
    return middlewares

这段代码最厉害的地方,不是"中间件很多",而是顺序本身有语义:

  • 先建线程与沙箱,再跑其他能力
  • 先做上下文压缩,再做后续推理
  • 标题在前,记忆在后
  • ClarificationMiddleware 被明确要求放最后

这不是简单的功能堆叠,而是运行时执行顺序的设计。

5.2 共享 runtime middleware

源码位置:

  • backend/packages/harness/deerflow/agents/middlewares/tool_error_handling_middleware.py:68-130
python 复制代码
def _build_runtime_middlewares(
    *,
    include_uploads: bool,
    include_dangling_tool_call_patch: bool,
    lazy_init: bool = True,
) -> list[AgentMiddleware]:
    middlewares: list[AgentMiddleware] = [
        ThreadDataMiddleware(lazy_init=lazy_init),
        SandboxMiddleware(lazy_init=lazy_init),
    ]

    if include_uploads:
        middlewares.insert(1, UploadsMiddleware())

    if include_dangling_tool_call_patch:
        middlewares.append(DanglingToolCallMiddleware())

    middlewares.append(LLMErrorHandlingMiddleware())

    if guardrails_config.enabled and guardrails_config.provider:
        provider_cls = resolve_variable(guardrails_config.provider.use)
        provider = provider_cls(**provider_kwargs)
        middlewares.append(
            GuardrailMiddleware(provider, fail_closed=guardrails_config.fail_closed, passport=guardrails_config.passport)
        )

    middlewares.append(SandboxAuditMiddleware())
    middlewares.append(ToolErrorHandlingMiddleware())
    return middlewares

这段代码的价值在于,它把 lead agent 和 subagent 的公共执行层抽了出来。

这说明 DeerFlow 不是在写"一个 Agent 的特例",而是在做"多个 Agent 可复用的运行时基底"。

5.3 它把失败当作系统主路径处理

源码位置:

  • backend/packages/harness/deerflow/agents/middlewares/tool_error_handling_middleware.py:19-65
python 复制代码
class ToolErrorHandlingMiddleware(AgentMiddleware[AgentState]):
    def _build_error_message(self, request: ToolCallRequest, exc: Exception) -> ToolMessage:
        tool_name = str(request.tool_call.get("name") or "unknown_tool")
        tool_call_id = str(request.tool_call.get("id") or _MISSING_TOOL_CALL_ID)
        detail = str(exc).strip() or exc.__class__.__name__

        content = (
            f"Error: Tool '{tool_name}' failed with {exc.__class__.__name__}: "
            f"{detail}. Continue with available context, or choose an alternative tool."
        )
        return ToolMessage(
            content=content,
            tool_call_id=tool_call_id,
            name=tool_name,
            status="error",
        )

    async def awrap_tool_call(...):
        try:
            return await handler(request)
        except GraphBubbleUp:
            raise
        except Exception as exc:
            return self._build_error_message(request, exc)

这个设计非常重要。

它不是"工具一报错就让 run 整体失败",而是把错误转成 ToolMessage,让模型继续在错误上下文里推理。

在真实 Agent 系统里,这种"失败可恢复"能力,远比再加几个工具更有价值。


六、上下文工程:DeerFlow 真正有技术含量的地方

DeerFlow 对上下文问题不是单点处理,而是分成三层:

  • 短期:线程内状态
  • 中期:摘要压缩
  • 长期:结构化用户记忆

6.1 短期状态:ThreadState

源码位置:

  • backend/packages/harness/deerflow/agents/thread_state.py:21-55
python 复制代码
def merge_artifacts(existing: list[str] | None, new: list[str] | None) -> list[str]:
    if existing is None:
        return new or []
    if new is None:
        return existing
    return list(dict.fromkeys(existing + new))


def merge_viewed_images(existing: dict[str, ViewedImageData] | None, new: dict[str, ViewedImageData] | None) -> dict[str, ViewedImageData]:
    if existing is None:
        return new or {}
    if new is None:
        return existing
    if len(new) == 0:
        return {}
    return {**existing, **new}


class ThreadState(AgentState):
    sandbox: NotRequired[SandboxState | None]
    thread_data: NotRequired[ThreadDataState | None]
    title: NotRequired[str | None]
    artifacts: Annotated[list[str], merge_artifacts]
    todos: NotRequired[list | None]
    uploaded_files: NotRequired[list[dict] | None]
    viewed_images: Annotated[dict[str, ViewedImageData], merge_viewed_images]

这里值得讲的不只是字段,而是 reducer 的设计:

  • merge_artifacts 会去重并保持顺序
  • merge_viewed_images 支持合并,也支持通过空字典清空

这说明 ThreadState 不是随手扩展的字段堆,而是认真考虑过状态演化规则的运行时容器。

6.2 长期记忆入口:MemoryMiddleware

源码位置:

  • backend/packages/harness/deerflow/agents/middlewares/memory_middleware.py:24-98
python 复制代码
class MemoryMiddleware(AgentMiddleware[MemoryMiddlewareState]):
    @override
    def after_agent(self, state: MemoryMiddlewareState, runtime: Runtime) -> dict | None:
        config = get_memory_config()
        if not config.enabled:
            return None

        thread_id = runtime.context.get("thread_id") if runtime.context else None
        if thread_id is None:
            config_data = get_config()
            thread_id = config_data.get("configurable", {}).get("thread_id")
        if not thread_id:
            return None

        messages = state.get("messages", [])
        filtered_messages = filter_messages_for_memory(messages)

        user_messages = [m for m in filtered_messages if getattr(m, "type", None) == "human"]
        assistant_messages = [m for m in filtered_messages if getattr(m, "type", None) == "ai"]
        if not user_messages or not assistant_messages:
            return None

        correction_detected = detect_correction(filtered_messages)
        reinforcement_detected = not correction_detected and detect_reinforcement(filtered_messages)
        queue = get_memory_queue()
        queue.add(
            thread_id=thread_id,
            messages=filtered_messages,
            agent_name=self._agent_name,
            correction_detected=correction_detected,
            reinforcement_detected=reinforcement_detected,
        )

这段代码体现了 DeerFlow 的一个重要判断:

长期记忆不是原样存所有消息,而是只保留"用户输入 + 最终 AI 响应"的有效部分。

这能大幅减少工具输出、系统噪声、半成品步骤对长期记忆的污染。

6.3 最值得单独写的一段:memory_flush_hook

源码位置:

  • backend/packages/harness/deerflow/agents/memory/summarization_hook.py:11-31
python 复制代码
def memory_flush_hook(event: SummarizationEvent) -> None:
    if not get_memory_config().enabled or not event.thread_id:
        return

    filtered_messages = filter_messages_for_memory(list(event.messages_to_summarize))
    user_messages = [message for message in filtered_messages if getattr(message, "type", None) == "human"]
    assistant_messages = [message for message in filtered_messages if getattr(message, "type", None) == "ai"]
    if not user_messages or not assistant_messages:
        return

    correction_detected = detect_correction(filtered_messages)
    reinforcement_detected = not correction_detected and detect_reinforcement(filtered_messages)
    queue = get_memory_queue()
    queue.add_nowait(
        thread_id=event.thread_id,
        messages=filtered_messages,
        agent_name=event.agent_name,
        correction_detected=correction_detected,
        reinforcement_detected=reinforcement_detected,
    )

这段代码是全项目最值得点名的工程亮点之一。

因为它解决了一个很现实的问题:

  • 对话太长,必须摘要
  • 但摘要掉的旧消息,又可能包含未来还需要记住的信息

DeerFlow 的答案是:

在消息被摘要删掉之前,先把有价值的部分送进长期记忆队列。

这就把"中期压缩"和"长期记忆"连接起来了。

很多 Agent 项目只做到摘要,但没处理"摘要以后还剩下什么"。

6.4 记忆不是同步写入,而是异步去抖队列

源码位置:

  • backend/packages/harness/deerflow/agents/memory/queue.py:27-170
python 复制代码
class MemoryUpdateQueue:
    def __init__(self):
        self._queue: list[ConversationContext] = []
        self._lock = threading.Lock()
        self._timer: threading.Timer | None = None
        self._processing = False

    def add(...):
        with self._lock:
            self._enqueue_locked(...)
            self._reset_timer()

    def _enqueue_locked(...):
        existing_context = next(
            (context for context in self._queue if context.thread_id == thread_id),
            None,
        )
        context = ConversationContext(...)
        self._queue = [c for c in self._queue if c.thread_id != thread_id]
        self._queue.append(context)

    def _process_queue(self) -> None:
        with self._lock:
            if self._processing or not self._queue:
                return
            self._processing = True
            contexts_to_process = self._queue.copy()
            self._queue.clear()

        updater = MemoryUpdater()
        for context in contexts_to_process:
            updater.update_memory(...)

这段实现的好处非常明确:

  • 同一线程的记忆更新可以合并
  • 高频对话不会每轮都立刻写 memory
  • 记忆更新不会阻塞主对话链路

这就是典型的"Agent 工程化细节":不是功能更炫,而是系统更稳。


七、Sandbox:Agent 真正"能动手"的前提

DeerFlow 的沙箱能力不只是"给模型一个 bash 工具",而是做了完整的路径语义和安全校验。

7.1 本地读路径会先做校验和映射

源码位置:

  • backend/packages/harness/deerflow/sandbox/tools.py:330-336
python 复制代码
def _resolve_local_read_path(path: str, thread_data: ThreadDataState) -> str:
    validate_local_tool_path(path, thread_data, read_only=True)
    if _is_skills_path(path):
        return _resolve_skills_path(path)
    if _is_acp_workspace_path(path):
        return _resolve_acp_workspace_path(path, _extract_thread_id_from_thread_data(thread_data))
    return _resolve_and_validate_user_data_path(path, thread_data)

这段代码虽然短,但非常能体现 DeerFlow 的思路:

  • 不是直接用宿主机路径
  • 先校验路径是否合法
  • 再按不同语义映射到 skills、ACP workspace 或 user-data

也就是说,Agent 面向的是一个"受控虚拟文件系统",不是宿主机文件系统裸奔。

7.2 bash 工具也不是直接放开

源码位置:

  • backend/packages/harness/deerflow/sandbox/tools.py:989-1035
python 复制代码
@tool("bash", parse_docstring=True)
def bash_tool(runtime: ToolRuntime[ContextT, ThreadState], description: str, command: str) -> str:
    try:
        sandbox = ensure_sandbox_initialized(runtime)
        if is_local_sandbox(runtime):
            if not is_host_bash_allowed():
                return f"Error: {LOCAL_HOST_BASH_DISABLED_MESSAGE}"
            ensure_thread_directories_exist(runtime)
            thread_data = get_thread_data(runtime)
            validate_local_bash_command_paths(command, thread_data)
            command = replace_virtual_paths_in_command(command, thread_data)
            command = _apply_cwd_prefix(command, thread_data)
            output = sandbox.execute_command(command)
            return _truncate_bash_output(mask_local_paths_in_output(output, thread_data), max_chars)
        return _truncate_bash_output(sandbox.execute_command(command), max_chars)
    except SandboxError as e:
        return f"Error: {e}"

这里能看到 DeerFlow 对 shell 执行的态度:

  • host bash 默认不是无条件开放
  • 命令里的路径会被校验和替换
  • 输出还会做截断和路径掩码

这不是"能跑就行"的 Agent 工具层,而是明显往可控运行时靠拢。

7.3 read_file / write_file / str_replace 也是受控的

源码位置:

  • backend/packages/harness/deerflow/sandbox/tools.py:1202-1325

节选如下:

python 复制代码
@tool("read_file", parse_docstring=True)
def read_file_tool(runtime, description, path, start_line=None, end_line=None) -> str:
    sandbox = ensure_sandbox_initialized(runtime)
    ensure_thread_directories_exist(runtime)
    if is_local_sandbox(runtime):
        thread_data = get_thread_data(runtime)
        validate_local_tool_path(path, thread_data, read_only=True)
        if _is_skills_path(path):
            path = _resolve_skills_path(path)
        elif _is_acp_workspace_path(path):
            path = _resolve_acp_workspace_path(path, _extract_thread_id_from_thread_data(thread_data))
        elif not _is_custom_mount_path(path):
            path = _resolve_and_validate_user_data_path(path, thread_data)
    content = sandbox.read_file(path)


@tool("write_file", parse_docstring=True)
def write_file_tool(runtime, description, path, content, append=False) -> str:
    sandbox = ensure_sandbox_initialized(runtime)
    ensure_thread_directories_exist(runtime)
    if is_local_sandbox(runtime):
        thread_data = get_thread_data(runtime)
        validate_local_tool_path(path, thread_data)
        if not _is_custom_mount_path(path):
            path = _resolve_and_validate_user_data_path(path, thread_data)
    with get_file_operation_lock(sandbox, path):
        sandbox.write_file(path, content, append)
    return "OK"

读写文件之所以能成为"平台能力",就在于这些边界都被提前处理过了。


八、Subagent:它不是概念,而是真能工作的执行系统

DeerFlow 的子代理不是"多套 prompt 套娃",而是完整的后台执行机制。

8.1 task_tool.py:子代理的入口

源码位置:

  • backend/packages/harness/deerflow/tools/builtins/task_tool.py:22-210
python 复制代码
@tool("task", parse_docstring=True)
async def task_tool(
    runtime: ToolRuntime[ContextT, ThreadState],
    description: str,
    prompt: str,
    subagent_type: str,
    tool_call_id: Annotated[str, InjectedToolCallId],
    max_turns: int | None = None,
) -> str:
    config = get_subagent_config(subagent_type)
    if config is None:
        return f"Error: Unknown subagent type '{subagent_type}'."

    skills_section = get_skills_prompt_section()
    if skills_section:
        config = replace(config, system_prompt=config.system_prompt + "\n\n" + skills_section)

    sandbox_state = runtime.state.get("sandbox")
    thread_data = runtime.state.get("thread_data")
    thread_id = runtime.context.get("thread_id") if runtime.context else None

    tools = get_available_tools(model_name=parent_model, subagent_enabled=False)

    executor = SubagentExecutor(
        config=config,
        tools=tools,
        parent_model=parent_model,
        sandbox_state=sandbox_state,
        thread_data=thread_data,
        thread_id=thread_id,
        trace_id=trace_id,
    )

    task_id = executor.execute_async(prompt, task_id=tool_call_id)
    writer = get_stream_writer()
    writer({"type": "task_started", "task_id": task_id, "description": description})

    while True:
        result = get_background_task_result(task_id)
        if result.status == SubagentStatus.COMPLETED:
            writer({"type": "task_completed", "task_id": task_id, "result": result.result})
            return f"Task Succeeded. Result: {result.result}"

这段代码说明,DeerFlow 的子代理具备几个关键特征:

  • 子代理会继承父级必要上下文
  • 子代理不会继续递归拿到 task 工具,避免无限套娃
  • 子代理的状态会通过事件流回到前端
  • 子代理的运行不是同步阻塞,而是后台执行 + 状态轮询

8.2 SubagentExecutor:真正的运行器

源码位置:

  • backend/packages/harness/deerflow/subagents/executor.py:128-248
python 复制代码
class SubagentExecutor:
    def __init__(self, config, tools, parent_model=None, sandbox_state=None, thread_data=None, thread_id=None, trace_id=None):
        self.config = config
        self.parent_model = parent_model
        self.sandbox_state = sandbox_state
        self.thread_data = thread_data
        self.thread_id = thread_id
        self.trace_id = trace_id or str(uuid.uuid4())[:8]
        self.tools = _filter_tools(tools, config.tools, config.disallowed_tools)

    def _create_agent(self):
        model_name = _get_model_name(self.config, self.parent_model)
        model = create_chat_model(name=model_name, thinking_enabled=False)
        middlewares = build_subagent_runtime_middlewares(lazy_init=True)
        return create_agent(
            model=model,
            tools=self.tools,
            middleware=middlewares,
            system_prompt=self.config.system_prompt,
            state_schema=ThreadState,
        )

    def _build_initial_state(self, task: str) -> dict[str, Any]:
        state = {"messages": [HumanMessage(content=task)]}
        if self.sandbox_state is not None:
            state["sandbox"] = self.sandbox_state
        if self.thread_data is not None:
            state["thread_data"] = self.thread_data
        return state

这个实现有两个特别好的地方:

  1. 子代理不是"另一个 prompt call",而是完整 agent
  2. 子代理直接复用了共享 runtime middleware 和 ThreadState

这使得 DeerFlow 的多智能体能力不是概念演示,而是框架级能力。


九、几个最值得放进博客的技术亮点

如果你打算把这篇文档进一步压成一篇更短的技术博客,我建议重点保留下面几组代码:

1. make_lead_agent() 的工厂式装配

位置:

  • backend/packages/harness/deerflow/agents/lead_agent/agent.py:280-356

亮点:

  • 动态模型选择
  • 动态工具组合
  • 动态中间件链
  • ThreadState 统一状态容器

2. 中间件链与共享 runtime builder

位置:

  • backend/packages/harness/deerflow/agents/lead_agent/agent.py:215-277
  • backend/packages/harness/deerflow/agents/middlewares/tool_error_handling_middleware.py:68-130

亮点:

  • 顺序语义明确
  • lead/subagent 共用一套底层 runtime 构造逻辑

3. ToolErrorHandlingMiddleware

位置:

  • backend/packages/harness/deerflow/agents/middlewares/tool_error_handling_middleware.py:19-65

亮点:

  • 把工具异常转成可恢复的 ToolMessage

4. ThreadState + memory_flush_hook + MemoryUpdateQueue

位置:

  • backend/packages/harness/deerflow/agents/thread_state.py:21-55
  • backend/packages/harness/deerflow/agents/memory/summarization_hook.py:11-31
  • backend/packages/harness/deerflow/agents/memory/queue.py:27-170

亮点:

  • 短期状态、中期摘要、长期记忆被有机地连在了一起

5. task_tool.py + SubagentExecutor

位置:

  • backend/packages/harness/deerflow/tools/builtins/task_tool.py:22-210
  • backend/packages/harness/deerflow/subagents/executor.py:128-248

亮点:

  • 子代理异步执行
  • 状态事件回流
  • 父上下文继承
  • 防止递归嵌套

6. useThreadStream()

位置:

  • frontend/src/core/threads/hooks.ts:204-530

亮点:

  • SDK 对接、事件消费、乐观更新、上下文提交都在一条链里

十、横向比较:DeerFlow 和其他同类产品差在哪

如果把 DeerFlow 放在几类常被一起讨论的系统旁边看,它的定位会更清楚。

10.1 DeerFlow vs LangGraph Platform

LangGraph Platform 更强调:

  • threads
  • runs
  • durable execution
  • persistence
  • streaming
  • human-in-the-loop

这些能力更接近基础设施平台。

DeerFlow 则在其上继续补了:

  • 工作区 UI
  • skills
  • sandbox
  • 结构化长期记忆
  • 子代理运行系统
  • artifacts / uploads / settings 管理

可以简单理解成:

  • LangGraph Platform 更像 Agent 基础设施
  • DeerFlow 更像建立在基础设施之上的开源 Agent Runtime 系统

10.2 DeerFlow vs OpenHands

OpenHands 的重心更偏向开发代理体验:

  • 代码编辑
  • 终端
  • 浏览器
  • Docker runtime
  • 面向仓库开发任务

DeerFlow 与它的差异在于:

  • 更强调 runtime 抽象
  • 更强调中间件和上下文工程
  • 长期记忆是一等公民
  • skills、memory、sandbox、subagent 的框架感更强

如果想要"直接拿来做开发代理",OpenHands 很自然。

如果想要"研究并改造一个 Agent 底座",DeerFlow 更有延展性。

10.3 DeerFlow vs Dify

Dify 更像 AI 应用平台,核心在:

  • workflow / chatflow
  • knowledge base
  • 低代码编排
  • 企业场景应用

DeerFlow 的重心则在:

  • 运行时治理
  • 子代理机制
  • 沙箱与工具层
  • 长对话上下文工程
  • 结构化长期记忆

一句话概括:

  • Dify 更适合业务应用交付
  • DeerFlow 更适合构建长期协作型 Agent 系统

10.4 DeerFlow vs AutoGen Studio

AutoGen Studio 官方定位更偏原型工具:

  • low-code
  • multi-agent team 原型
  • playground / gallery
  • 强调快速试验

DeerFlow 则明显更偏工程系统:

  • 状态
  • 运行时
  • 沙箱
  • 管理 API
  • 失败恢复

可以说:

  • AutoGen Studio 擅长把想法快速搭出来
  • DeerFlow 擅长把系统跑起来并长期维护下去

十一、最终评价:为什么 DeerFlow 值得认真看

如果只把 DeerFlow 当成"又一个开源 Agent 项目",其实低估它了。

它真正值得研究的,是它在这些关键问题上表现出了难得的边界感:

  • 前端不自己接管 runtime,而是围绕 runtime 做工作区
  • Gateway 不和 Harness 混成一团
  • 上下文压缩和长期记忆不是互相打架,而是用 hook 连起来
  • 工具不是裸奔,而是带路径语义和安全约束
  • 子代理不是概念,而是有 executor、有事件流、有取消机制
  • 失败不是异常边缘,而是系统主路径的一部分

这些设计共同说明了一件事:

DeerFlow 已经不只是一个 Demo,而是一套开始具备平台气质的 Agent 系统。

如果你正在寻找一个值得精读源码、适合二次开发、又足够有工程味的 Agent 项目,DeerFlow 很值得放进名单。

本文只是让你产生兴趣去看源码,我一直以为,光靠看是做不到学习的,正如老夫子言:"学而不思则罔,思而不学则殆"。只有源码才能带给你震撼,而且相较于以往,现在在AI的加持下,读懂源码也并不是什么难事,这篇文章就是使用AI总结优化的,目的是为了让大家学会设计的思想,而不是只是会写代码(虽然现在古法编程已经大部分被AI取代了),行文至此,祝大家都有所收获。

相关推荐
杨艺韬3 小时前
vLLM内核探秘-第7章 模型加载与权重管理
agent
杨艺韬3 小时前
vLLM内核探秘-第13章 量化引擎:精度与速度的平衡
agent
杨艺韬3 小时前
vLLM内核探秘-第18章 设计模式与架构哲学
agent
杨艺韬3 小时前
vLLM内核探秘-第10章 前缀缓存:零开销的加速
agent
杨艺韬4 小时前
Harness Engineering-第4章 上下文工程:比 Prompt Engineering 更重要的事
agent
杨艺韬4 小时前
vLLM内核探秘-第9章 采样与输出处理
agent
杨艺韬4 小时前
Harness Engineering-前言
agent
杨艺韬4 小时前
Harness Engineering-第2章 Agent 架构模式全景
agent
杨艺韬4 小时前
Harness Engineering-第3章 Agent Loop:心跳与决策循环
agent