import { Callout, Cards, Steps } from "nextra/components";
# Quick Start
This guide shows you how to use the DeerFlow Harness programmatically — not
through the App UI, but by importing and calling the harness directly in
Python.
The DeerFlow Harness is the Python SDK and runtime foundation. This quick start walks you through the key APIs for running an agent, streaming its output, and working with threads.
## 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
```
## Configuration
All harness behaviors are driven by `config.yaml`. At minimum, you need at least one model configured:
```yaml
# config.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
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
```
Copy `config.example.yaml` to `config.yaml` and fill in your API key.
## Running the harness
The primary entry point for the DeerFlow Harness is `DeerFlowClient`. It manages thread state, invokes the Lead Agent, and streams the response.
### Import and configure
```python
import asyncio
from deerflow.client import DeerFlowClient
from deerflow.config import load_config
# Load config.yaml from the current directory or DEER_FLOW_CONFIG_PATH
load_config()
client = DeerFlowClient()
```
### Create a thread
```python
thread_id = "my-thread-001"
```
Thread IDs are arbitrary strings. Reusing the same ID continues the existing conversation (if a checkpointer is configured).
### Send a message and stream the response
```python
async def run():
async for event in client.astream(
thread_id=thread_id,
message="Research the top 3 open-source LLM frameworks and summarize them.",
config={
"configurable": {
"model_name": "gpt-4o",
"thinking_enabled": False,
"is_plan_mode": True,
"subagent_enabled": True,
}
},
):
print(event)
asyncio.run(run())
```
## Configurable options
The `config.configurable` dict controls per-request behavior:
| Key | Type | Default | Description |
|---|---|---|---|
| `model_name` | `str \| None` | first model in config | Model to use for this request |
| `thinking_enabled` | `bool` | `True` | Enable extended thinking mode (if supported) |
| `reasoning_effort` | `str \| None` | `None` | Reasoning effort level (model-specific) |
| `is_plan_mode` | `bool` | `False` | Enable TodoList middleware for task tracking |
| `subagent_enabled` | `bool` | `False` | Allow the agent to delegate subtasks |
| `max_concurrent_subagents` | `int` | `3` | Maximum parallel subagent calls per turn |
| `agent_name` | `str \| None` | `None` | Name of a custom agent to load |
## Streaming event types
`client.astream()` yields events from the LangGraph runtime. The key event types are:
| Event type | Description |
|---|---|
| `messages` | Individual message chunks (text, thinking, tool calls) |
| `thread_state` | Thread state updates (title, artifacts, todo list) |
Message chunks contain the token stream as the agent generates its response.
## Working with a custom agent
If you have defined a custom agent, pass its `name` in the configurable:
```python
async for event in client.astream(
thread_id="thread-002",
message="Analyze the attached CSV and generate a summary chart.",
config={
"configurable": {
"agent_name": "data-analyst",
"subagent_enabled": True,
}
},
):
...
```
The custom agent's configuration (model, skills, tool groups) is loaded automatically from `agents/data-analyst/config.yaml`.
## Next steps