Deep Agent
A planning-capable RocketRide agent built on the Deep Agents library, with optional managed sub-agents for hierarchical delegation.
About Deep Agents
Deep Agents is an open-source agent framework built on LangChain and LangGraph. It layers the machinery long-running tasks need — upfront planning, state that persists across steps, and long-context management — on top of the standard tools-plus-LLM agent loop.
What it does
Runs an agent loop via deepagents.create_deep_agent (built on LangChain/LangGraph), which layers strategic planning, persistent state, and long-context management on top of the standard LangChain tool-calling loop. The package ships two node variants:
- Deep Agent (
agent_deepagent): the orchestrator. Consumesquestionsand producesanswers, runs standalone with its own tools, and registers as a tool (classType: ["agent", "tool"]) so a parent agent can delegate to it via<nodeId>.run_agent. - Deep Agent Subagent (
agent_deepagent_subagent): a managed worker (classType: ["deepagent"]). It has noquestionslane and cannot be invoked directly or called as a tool; it must be wired into a Deep Agent via thedeepagentinvoke channel, which delegates to it based on itsdescription.
Sub-agents are optional: with none connected, the Deep Agent behaves as a standard single-agent node. Inference is routed through the host LLM channel using a JSON envelope protocol, so any LLM that can follow JSON instructions works; native function-calling support is not required. Agent lifecycle progress (tool calls, LLM calls, agent steps) is streamed as SSE thinking events.
Example pipelines
Research assistant with an HTTP tool
chat → agent_deepagent → response_answers
llm_anthropic is wired to llm and tool_http_request to tool. Chat
questions reach the agent, which can call the HTTP tool before returning an
answer.
Hierarchical team with specialists
webhook → agent_deepagent → response, plus two Deep Agent Subagents on the
deepagent channel: a researcher (tools: tool_exa_search) and a coder
(tools: tool_python, tool_github), each with its own llm. The
orchestrator breaks the task apart and delegates each piece to the sub-agent
whose Description matches, and the plural tool_calls envelope lets it
delegate to several sub-agents in parallel in a single turn.
Agent-callable research service
A parent agent (e.g. agent_rocketride) with this Deep Agent connected as a
tool. With a filled-in Agent description, the parent invokes
<nodeId>.run_agent when it hits a question needing deep multi-step
research rather than attempting it in its own loop.
Connections
Deep Agent
| Channel | Required | Description |
|---|---|---|
llm | yes (min 1) | LLM used by the agent |
tool | no | Tools available to the agent (via control-plane invoke) |
deepagent | no | Deep Agent Subagent nodes for hierarchical delegation |
Deep Agent Subagent
| Channel | Required | Description |
|---|---|---|
llm | yes (min 1) | LLM this sub-agent thinks with |
tool | no | Tools available to this sub-agent |
The sub-agent's LLM and tool channels are independent of the orchestrator's. When the orchestrator delegates, the sub-agent's LLM and tool calls are routed back through this node's own channels.
Lanes
| Lane in | Lane out | Description |
|---|---|---|
questions | answers | Send the agent a task, receive its final answer |
Deep Agent only. The Subagent declares no lanes; it is driven by an orchestrator, not by direct questions.
As a tool
The Deep Agent (not the Subagent) exposes itself as an invokable tool, <nodeId>.run_agent, so parent agents can delegate to it in nested pipelines.
- Input:
{query: string, context?: object}.querymust be a non-empty string;context, when provided, is attached to the question as aRocketRide.agent.tool_context.v1JSON payload. - Output:
{content, meta, stack}.
When agent_description is non-empty it is included in the tool's description so parent agents can select this agent correctly.
Configuration
Both variants are steered the same way: by default you add Instructions, and turning on Advanced Mode swaps them for direct prompt fields. The field-level details:
Description (Subagent)
The most important field on a sub-agent. The orchestrating Deep Agent reads only this text when deciding which sub-agent gets a task — it cannot see the sub-agent's prompt, tools, or LLM. Keep it specific and action-oriented: "Searches the web and summarizes findings with sources" gets routed work; "helper agent" never gets picked. If a sub-agent sits idle while the orchestrator does everything itself, fix this field first.
Agent description (Deep Agent)
The same idea one level up: when the Deep Agent is used as a tool by a parent
agent, this text is folded into the run_agent tool description so the
caller can decide when to invoke it. Leave it blank if nothing calls this
agent as a tool.
Instructions, System prompt & Advanced Mode
With Advanced Mode off (the default), you steer the agent by adding
Instructions — each non-empty line is appended, on its own line, to the
built-in system prompt that already handles planning and state. With Advanced
Mode on, the Instructions list is replaced by the raw prompt fields
(system_prompt, and agent_description on the orchestrator) for full
control; a blank system_prompt falls back to the built-in default. Stay in
default mode unless the built-in prompt is actively in your way — replacing
it discards the planning behavior that makes Deep Agents worth choosing.
Require tool call
Smaller or weaker planning models occasionally narrate a multi-step tool
chain in prose instead of actually calling the tools, producing a
plausible-looking but ungrounded answer. When this toggle is on, any run that
produces an answer without invoking at least one tool fails with a
RocketRide.agent.guard.v1 error instead of delivering the ungrounded text.
Off by default; enable it for determinism-critical pipelines. The guard
counts real tool invocations only — internal/local reads (for example the
wave agent's memory.peek) do not satisfy it.
Notes
Tool calling protocol
The host LLM is opaque to the driver, so tool calling uses a JSON envelope protocol: each LLM call is prefixed with a system preamble. Tool calls are JSON; an answer to the person is not.
- Single tool call:
{"type":"tool_call","name":"server.tool","args":{...}} - Parallel tool calls:
{"type":"tool_calls","calls":[{"name":"...","args":{...}}, ...]} - Final answer:
FINAL>>>followed by the answer as plain text
Why the answer left JSON. Arguments are structured data, where JSON earns its keep. An answer is prose, and the JSON form asked the model to escape a whole markdown table into a string value — one missed inner quote and the envelope stopped parsing, so the person read the envelope instead of the answer. After FINAL>>> there is nothing left to escape, and the text is delivered exactly as written. The {"type":"final","content":"..."} shape is still accepted by the parser for any model that emits it; it is no longer what the preamble asks for.
"Exactly as written" is literal: only the separator between the marker and the answer is removed — one newline, or one space on the same line. Whitespace after that belongs to the answer, so FINAL>>> followed by an indented line still arrives as a markdown code block.
The sentinel is matched at the start of a line and is checked before any JSON is parsed. Before it, because an answer may legitimately open with {; anchored, because protocol text travels — a delegation carrying instructions, a note quoting the format, a transcript replayed into a prompt — and an unanchored match would read a tool call that merely mentions FINAL>>> as a final answer and drop the call. A sentinel with nothing after it is not an answer, and goes back through the retry loop.
The plural tool_calls form dispatches all entries concurrently (LangGraph's async ToolNode fans them out via asyncio.gather), which is what unlocks parallel sub-agent delegation in a single turn.
Up to 3 attempts are made when the LLM produces malformed JSON. Each retry names the character that broke the parse and quotes the text around it, and what it asks for next depends on what was attempted: a broken answer is pointed at FINAL>>>, where nothing can break again, while a broken tool call is asked for the same call repaired — telling the model to answer in prose instead would trade the work for a sentence about it. A tolerant parser extracts the first balanced JSON object, rescuing responses wrapped in markdown fences, followed by trailing prose, or stacked with a stray second object (a common failure mode: a duplicate call or hallucinated final appended after a tool_call). An unparseable envelope that unmistakably opens as a final is salvaged for its content rather than printed at the person; a malformed TOOL call never is, since rescuing one would turn work the crew intended to do into a sentence claiming it was done.
Host tool descriptors are converted to LangChain BaseTool instances with typed Pydantic input schemas built from each tool's JSON-Schema inputSchema; tool execution and LLM calls are bridged off the event loop via asyncio.to_thread so concurrent calls do not serialize.
Hierarchical delegation
Connect one or more Deep Agent Subagent nodes to the deepagent invoke channel to turn the Deep Agent into an orchestrator. On each run:
- The orchestrator fans out a
describeinvoke to every connected Subagent node. - Each sub-agent returns its name, description, system prompt, instructions, and a reference to its own engine channels.
- The orchestrator builds a
deepagents.middleware.subagents.SubAgentrecord per descriptor, wiring each sub-agent's LLM and tools to its own channels, and passes them tocreate_deep_agent(subagents=...). - The orchestrator's LLM gains a
task(description, subagent_type)tool it calls to delegate work. Each sub-agent runs in its ownAgentContextthat inherits the run metadata, so SSE events route back to the same logical run.
Give each sub-agent its own LLM, tools, and a clear description: the description is the only signal the orchestrator uses to choose a delegate. A Subagent can be connected to multiple orchestrators simultaneously; each orchestrator independently includes it in its own hierarchical run. A describe failure on one node is logged and skipped, not fatal to the run.
Observability
The driver emits SSE thinking events throughout a run: host-tool discovery count, sub-agent collection count, agent start, per-tool start/completion/error (with tool name and input length), LLM call start/completion/error, and agent thinking/done transitions.
Upstream docs
Schema
Deep Agent (services.agent.json)
| Field | Type | Description | Default |
|---|---|---|---|
advanced_mode | boolean | Advanced Mode When enabled, replace the Instructions list with direct Agent Description and System Prompt fields for full control. | false |
agent_description | string | Agent description What does this agent do? Describe its purpose and capabilities, this helps parent agents select and invoke it correctly. | "" |
instructions | array | Instructions Additional instructions to guide the agent. Each line is appended to the system prompt. | |
require_tool_call | boolean | Require tool call Require the agent to invoke at least one tool before answering. When on, a run that answers without calling any tool fails with a guard error. Use for determinism-critical pipelines where an ungrounded or narrated answer must never be delivered. Off by default. | false |
system_prompt | string | System prompt Instructions that define this agent's role and behaviour. Leave blank to use the default. | "" |
Deep Agent Subagent (services.subagent.json)
| Field | Type | Description | Default |
|---|---|---|---|
advanced_mode | boolean | Advanced Mode When enabled, replace the Instructions list with a direct System Prompt field for full control. | false |
description | string | Description The orchestrator reads this description to decide when to delegate to this sub-agent. Keep it specific and action-oriented, this is the only signal the orchestrator uses to pick a sub-agent. | "" |
instructions | array | Instructions Additional instructions to guide this sub-agent. Each line is appended to the system prompt. | |
system_prompt | string | System prompt Instructions that define this sub-agent's role and behaviour. Leave blank to use the default. | "" |
Dependencies
deepagentslangchainlangchain-corepydantic
