Skill: bevanding/deerflow (version 1.1.3)

--- name: deerflow description: "Deep research and async task execution via DeerFlow LangGraph engine. Submit multi-step research tasks through a lightweight API-only Docker deployment (no frontend). Triggers: /deerflow, deep research, async research, deerflow." version: 1.1.3 license: mit-0 metadata: openclaw: requires: bins: [python3] anyBins: - git # Required only for initial DeerFlow deployment (optional) - docker # Required only for initial DeerFlow deployment (optional) env: - DEERFLOW_URL - DEERFLOW_LANGGRAPH - DEERFLOW_ASSISTANT_ID - DEERFLOW_MODEL - DEERFLOW_RECURSION os: [linux, darwin] --- # DeerFlow Integration ## What This Skill Does DeerFlow is a LangGraph-based deep research engine that chains web search, reasoning, and synthesis into structured reports. This skill provides OpenClaw integration for **submitting and monitoring** research tasks against a running DeerFlow API. **Runtime contract**: The skill itself runs entirely locally (python3 only) and communicates with DeerFlow over HTTP. It does not install or run any external Docker images — those are operated independently by the user as the DeerFlow host. **This skill does NOT** install DeerFlow or manage its services. It assumes a DeerFlow instance is already running and reachable. ## Prerequisites > ⚠️ **External dependency — your DeerFlow host.** DeerFlow itself runs as a separate service on your infrastructure (or a VPS). Its Docker images are maintained by the [bytedance/deer-flow](https://github.com/bytedance/deer-flow) project. Review their security posture before deployment. ### Required at runtime | Binary | Purpose | Notes | |--------|---------|-------| | `python3` | Run `submit_task.py` / `check_status.py` | Declared in skill metadata | ### Required only for initial DeerFlow deployment (one-time setup) | Binary | Purpose | Notes | |--------|---------|-------| | `git` | Clone the DeerFlow repository | Not needed once DeerFlow is deployed | | `docker` | Run DeerFlow services | Not needed once DeerFlow is running | If DeerFlow is already running somewhere (yours, a colleague's, or a cloud instance), skip straight to [Quick Start](#quick-start) — no git/docker needed. ## Deploying DeerFlow (if you don't have one) > 🔴 **Security note:** The following steps pull Docker images and run services from the `bytedance/deer-flow` GitHub repository. You are responsible for reviewing those images and configurations before running them in your environment. ### 1. Clone and configure ```bash git clone https://github.com/bytedance/deer-flow.git cd deer-flow cp .env.example .env ``` Edit `.env` with your model API keys: ```bash # Required: at least one LLM provider OPENAI_API_KEY=sk-... # Or MiniMax MINIMAX_API_KEY=... MINIMAX_API_BASE=https://api.minimax.com # Optional: Tavily for web search TAVILY_API_KEY=tvly-... ``` ### 2. Start API-only services ```bash # No nginx, no frontend — just gateway + langgraph docker compose up -d deer-flow-gateway deer-flow-langgraph ``` Verify: ```bash curl http://localhost:2024/openapi.json | head # should return OpenAPI spec curl http://localhost:8001/health # should return 200 ``` ### 3. Test with a manual task ```bash curl -X POST http://localhost:2024/threads \ -H "Content-Type: application/json" \ -d '{}' # Returns: { "thread_id": "..." } ``` Then submit a task: ```bash curl -X POST http://localhost:2024/threads/{thread_id}/runs \ -H "Content-Type: application/json" \ -d '{ "assistant_id": "lead_agent", "input": { "messages": [{ "type": "human", "content": [{ "type": "text", "text": "Your research query here" }] }] }, "config": { "recursion_limit": 200, "configurable": { "model_name": "minimax-m2.7", "thinking_enabled": true, "is_plan_mode": false, "subagent_enabled": false } } }' # Returns: { "run_id": "..." } ``` Poll for completion: ```bash curl http://localhost:2024/threads/{thread_id}/runs/{run_id} ``` When status = `success`, fetch results: ```bash curl http://localhost:2024/threads/{thread_id}/history ``` ## Quick Start ``` /deerflow <research topic> ``` Example: `/deerflow Analyze the Chinese AI companion market` The skill returns a `thread_id` and `run_id` for status tracking. ## Architecture This skill targets the **minimal API-only DeerFlow deployment**. Only two services are relevant to this skill: | Service | Port | Role | |---------|------|------| | `deer-flow-gateway` | 8001 | Business logic & channel glue | | `deer-flow-langgraph` | 2024 | Core agent orchestration (the only endpoint this skill calls) | ## Model Configuration Set `model_name` in the `configurable` block: | Model | Config Value | Notes | |-------|-------------|-------| | MiniMax M2.7 | `minimax-m2.7` | Default, reasoning-capable | | MiniMax M2.5 | `minimax-m2.5` | Lighter alternative | | Kimi | `kimi` | Requires DeerFlow `.env` to have Kimi credentials | Set `thinking_enabled: true` to enable extended chain-of-thought reasoning (recommended for research tasks). ## Skill Scripts This skill includes two helper scripts in `scripts/`: ### submit_task.py ```bash cd ~/.openclaw/workspace/skills/deerflow python3 scripts/submit_task.py "Your research topic" # Returns thread_id and run_id ``` ### check_status.py ```bash python3 scripts/check_status.py <thread_id> <run_id> # Polls until completion, then prints the full report ``` ## OpenClaw Tool Injection The skill is auto-injected into OpenClaw as the `deerflow` tool. OpenClaw agents call it directly when the user triggers the keyword. ## Resource Comparison | Deployment | Services | RAM Est. | Use Case | |-----------|----------|----------|----------| | **API-only (this skill)** | gateway + langgraph | ~2 GB | Self-hosted agents, VPS | | Full stack | + nginx + frontend | ~4+ GB | Team shared UI | ## Troubleshooting ### LangGraph returns 404 Verify the container is healthy: ```bash docker ps | grep langgraph curl http://localhost:2024/openapi.json ``` ### Task hangs or returns "error" status Check LangGraph logs: ```bash docker logs deer-flow-langgraph --tail 50 ``` ### Model API errors Ensure credentials in DeerFlow's `.env` are valid and the `model_name` in your request matches a configured provider. ## File Structure ``` skills/deerflow/ ├── SKILL.md # This file └── scripts/ ├── submit_task.py # Submit a research task └── check_status.py # Poll and retrieve results ```

path: _meta.json
content: ownerId: kn7fy7jd9vd0x7wvpxgfxye1pn827f50
slug: deerflow
version: 1.1.2
publishedAt: 1775323540974

path: scripts/check_status.py
content: #!/usr/bin/env python3
import json, sys, urllib.request, urllib.error, os

LANGGRAPH_URL = os.environ.get("DEERFLOW_LANGGRAPH", "http://localhost:2024")


def check_status(thread_id, run_id=None):
    url = f"{LANGGRAPH_URL}/threads/{thread_id}"
    if run_id:
        url += f"/runs/{run_id}"
    else:
        url += "/runs"

    req = urllib.request.Request(url, headers={"Content-Type": "application/json"})
    try:
        with urllib.request.urlopen(req) as response:
            return json.loads(response.read().decode("utf-8"))
    except urllib.error.URLError as e:
        print(f"Error: {e}")
        return None


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python3 check_status.py <thread_id> [run_id]")
        sys.exit(1)

    thread_id = sys.argv[1]
    run_id = sys.argv[2] if len(sys.argv) > 2 else None

    result = check_status(thread_id, run_id)
    if result:
        print(json.dumps(result, indent=2))


path: scripts/config.yaml
content: # DeerFlow Service Configuration
# Note: Model API keys are managed by the DeerFlow deployment,
# not by this skill. Configure them in DeerFlow's own .env file.

deerflow:
  # DeerFlow service URL (Nginx public gateway)
  url: "${DEERFLOW_URL:-http://localhost:2026}"

  # LangGraph internal API URL
  langgraph_url: "${DEERFLOW_LANGGRAPH_URL:-http://localhost:2024}"

  # Default assistant ID
  assistant_id: "lead_agent"

  # Default model (must match a model configured in DeerFlow)
  model: "${DEERFLOW_MODEL:-minimax-m2.7}"

  # Max recursion depth for agent loops
  recursion_limit: 200

  # API request timeout (ms)
  timeout: 30000

  # Number of retries on failure
  retries: 3

webhook:
  # Webhook listener port (built into deerflow-bridge, not this skill)
  port: 3002
  path: "/webhook/deerflow"
  public_url: "${WEBHOOK_PUBLIC_URL:-http://localhost:3002}"

openclaw:
  # OpenClaw callback URL (for deerflow-bridge notifications)
  url: "${OPENCLAW_URL:-http://localhost:18789}"
  notify_endpoint: "/api/notify"


path: scripts/submit_task.py
content: #!/usr/bin/env python3
"""
Submit a deep research task to DeerFlow.

Usage:
    python3 submit_task.py "<task description>"

Environment variables:
    DEERFLOW_URL         DeerFlow public gateway URL (default: http://localhost:2026)
    DEERFLOW_LANGGRAPH   LangGraph internal API URL (default: http://localhost:2024)
    DEERFLOW_MODEL       Model name to use (default: minimax-m2.7)
    DEERFLOW_RECURSION   Max recursion limit (default: 200)
"""
import json
import sys
import urllib.request
import urllib.error
import os

# Configuration: reads from environment variables, defaults to local DeerFlow stack.
DEERFLOW_URL = os.environ.get("DEERFLOW_URL", "http://localhost:2026")
LANGGRAPH_URL = os.environ.get("DEERFLOW_LANGGRAPH", "http://localhost:2024")
ASSISTANT_ID = os.environ.get("DEERFLOW_ASSISTANT_ID", "lead_agent")
MODEL_NAME = os.environ.get("DEERFLOW_MODEL", "minimax-m2.7")
RECURSION_LIMIT = int(os.environ.get("DEERFLOW_RECURSION", "200"))


def create_thread() -> str | None:
    """Create a new LangGraph thread. Returns thread_id or None on failure."""
    url = f"{LANGGRAPH_URL}/threads"
    data = json.dumps({}).encode("utf-8")
    req = urllib.request.Request(
        url, data=data, headers={"Content-Type": "application/json"}
    )
    try:
        with urllib.request.urlopen(req) as response:
            result = json.loads(response.read().decode("utf-8"))
            thread_id = result.get("thread_id")
            if not thread_id:
                print(f"Error: no thread_id in response: {result}")
            return thread_id
    except urllib.error.HTTPError as e:
        body = e.read().decode("utf-8")
        print(f"HTTP error creating thread ({e.code}): {body}")
    except urllib.error.URLError as e:
        print(f"Connection error creating thread: {e.reason}")
    return None


def submit_task(thread_id: str, task_description: str) -> str | None:
    """Submit a task to an existing thread. Returns run_id or None on failure."""
    url = f"{LANGGRAPH_URL}/threads/{thread_id}/runs"
    payload = {
        "assistant_id": ASSISTANT_ID,
        "input": {
            "messages": [
                {
                    "type": "human",
                    "content": [{"type": "text", "text": task_description}],
                }
            ]
        },
        "config": {
            "recursion_limit": RECURSION_LIMIT,
            "configurable": {
                "model_name": MODEL_NAME,
                "thinking_enabled": True,
                "is_plan_mode": False,
                "subagent_enabled": False,
            },
        },
    }
    data = json.dumps(payload).encode("utf-8")
    req = urllib.request.Request(
        url, data=data, headers={"Content-Type": "application/json"}
    )
    try:
        with urllib.request.urlopen(req) as response:
            result = json.loads(response.read().decode("utf-8"))
            run_id = result.get("run_id")
            if not run_id:
                print(f"Error: no run_id in response: {result}")
            return run_id
    except urllib.error.HTTPError as e:
        body = e.read().decode("utf-8")
        print(f"HTTP error submitting task ({e.code}): {body}")
    except urllib.error.URLError as e:
        print(f"Connection error submitting task: {e.reason}")
    return None


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python3 submit_task.py <task description>")
        sys.exit(1)

    print(f"DeerFlow: {DEERFLOW_URL} | LangGraph: {LANGGRAPH_URL} | "
          f"Model: {MODEL_NAME} | Recursion limit: {RECURSION_LIMIT}")

    thread_id = create_thread()
    if not thread_id:
        sys.exit(1)
    print(f"Thread created: {thread_id}")

    run_id = submit_task(thread_id, sys.argv[1])
    if not run_id:
        sys.exit(1)

    result = {
        "thread_id": thread_id,
        "run_id": run_id,
        "status": "submitted",
        "deerflow_url": DEERFLOW_URL,
    }
    print(f"\n{json.dumps(result, indent=2)}")


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)