Your prompt has crossed the frontend, passed through the Gateway, and become a run. Now it reaches make_lead_agent(). This stop is about how runtime options plus application config become a runnable LangGraph agent graph.
Hold onto one idea: this is an assembly line, not the brain. Reasoning happens later, inside the model-tool-middleware loop. This function only bolts the parts together and hands off the compiled graph. It does not even run the graph itself.
flowchart TD FE["Frontend / API"] --> GW["DeerFlow Gateway · run_agent()"] GW --> MK["make_lead_agent(config)"] MK -->|"compat: resolve AppConfig"| RC["_get_runtime_config(config)"] RC --> MLA["_make_lead_agent(config, app_config=...)"] MLA --> CA["create_agent(model, tools, middleware, prompt, ThreadState)"] CA --> G["compiled LangGraph agent graph"]
One factory, two entry points
Open agent.py and you’ll find a pair of near-twins: make_lead_agent() and _make_lead_agent(). The underscore is not cosmetic: it marks the most important boundary in the file.
make_lead_agent(config) takes a single argument because LangGraph Server requires graph factories to be callable from a config-only entry (see langgraph.json ). Its job is light: resolve an AppConfig (prefer the runtime-injected one, fall back to get_app_config()), then hand off to the underscore version. It is a compatibility adapter.
_make_lead_agent(config, *, app_config) is the real assembly function. That * makes app_config keyword-only — because both arguments are “config-like,” and positional calls are too easy to confuse. Its work is deterministic: build the graph, step by step, from runtime options plus application config.
How the runtime options get assembled
_get_runtime_config() merges config["configurable"] and config["context"]. Note this is not a mathematical union — it’s ordered: lay down configurable first, then let context overwrite same-named keys.
The fields it reads are per-run options, not global application settings:
model_name / model
thinking_enabled
reasoning_effort
is_plan_mode
subagent_enabled
max_concurrent_subagents
is_bootstrap
agent_name
How the model is chosen
Model resolution has a clear priority:
request model_name
> custom agent config model
> AppConfig.models[0]
If thinking_enabled=True but the selected model does not support thinking, the factory logs a warning and turns thinking_enabled back off. You can ask for thinking; the model still has to support it.
The final formula
Once every part is in place, the line emits this:
create_agent(
model=create_chat_model(...),
tools=filtered_tools,
middleware=build_middlewares(...),
system_prompt=apply_prompt_template(...),
state_schema=ThreadState,
)
The mental model, in one line:
Lead Agent graph = model + tools + middleware + system_prompt + ThreadState
create_agent() provides the standard model-and-tool loop. DeerFlow supplies the materials (model, tools) and the policies (middleware, prompt, state schema).
Clean assembly · _make_lead_agent
- receives an explicit
AppConfig - assembles deterministically from runtime options
- easy to test, easy to reason about
Compat cost · make_lead_agent
- single-arg ABI (LangGraph Server requires it)
app_configvia runtime config + global fallbackcfgis overloaded;configis mutated in place
Where it will trip you up
cfgis an overloaded name. It carries per-run options, but may also contain infrastructure fields likeapp_config,thread_id, orrun_id. Don’t assume whatcfgholds.configis mutated in place. The factory writesmetadataandcallbacksinto it — don’t treat it as read-only.- Bootstrap vs. custom agent is easy to mix up.
setup_agentshows up only in the bootstrap flow;update_agentonly when chatting with an existing custom agent. Pick the wrong one and you call the wrong tool. - Middleware order is assembled elsewhere, yet it’s part of the final behavior. Touch this line and you may quietly change runtime semantics. The middleware chapter returns to that risk.
That is the assembly line. Its best quality is being boring: deterministic, predictable, and free of hidden surprises. Before we take apart middleware, the next stop looks at tool assembly, because what the model can call decides what this graph can actually do.