Summary
Add a third AI mode to manolo-bot using LangChain's Deep Agents harness (deepagents package). This mode provides planning tools (todo list), sub-agents, and a virtual filesystem, enabling the bot to handle more complex, multi-step tasks than the current agent mode.
Currently the project has two modes:
llm: LLMBot — simple LLM with manual tool execution loop
agent: LLMAgent — uses langchain.agents.create_agent (LangGraph-based, auto tool loop)
The new mode will be deep_agent: LLMDeepAgent — uses deepagents.create_deep_agent with the full harness stack (todo list, sub-agents, virtual filesystem).
The default mode will be agent (not llm).
Background — What are Deep Agents?
LangChain Deep Agents is a batteries-included agent harness built on top of create_agent. It adds four key capabilities:
- Detailed system prompt — A general-purpose prompt inspired by Claude Code
- Planning tool — A no-op
TodoListMiddleware that keeps the agent on track for long tasks
- Sub-agents — A
task tool that spawns isolated sub-agents for focused subtasks
- Virtual filesystem — Read/write/edit/search over a pluggable backend (
StateBackend protocol)
Plus human-in-the-loop steering, context management, and code execution.
Official resources:
Implementation Tasks
1. Add deepagents dependency
File: pyproject.toml
dependencies = [
...
"deepagents>=0.7",
]
Then run uv sync --dev to install.
2. Configuration — manolo_bot/config.py
Add a new StringField for mode selection:
ai_mode = StringField("AI_MODE", default="agent", allowed_values=["llm", "agent", "deep_agent"])
Keep the existing agent_mode boolean for backwards compatibility. Add logic so that:
- If
AI_MODE is explicitly set → use it
- Else if
AGENT_MODE=True → treat as ai_mode="agent"
- Else if
AGENT_MODE=False (or unset) → default to ai_mode="agent" (the new default)
Also add a config field for the deep agent workspace path (separate from DOCUMENT_STORAGE_PATH which is for temp document handling):
deep_agent_workspace_path = StringField(
"DEEP_AGENT_WORKSPACE_PATH",
default=os.path.join(tempfile.gettempdir(), "manolo_bot", "workspace")
)
3. New module — manolo_bot/ai/llmdeepagent.py
Create a new class LLMDeepAgent that extends LLMAgent:
class LLMDeepAgent(LLMAgent):
Only override initialize_async_resources(). The parent LLMAgent already implements answer_message(), answer_image_message(), answer_voice_message(), answer_document_message() — they all call self.agent.ainvoke(...) which works the same way for both create_agent and create_deep_agent.
What initialize_async_resources() should do:
- Call
super().initialize_async_resources() to connect MCP and load tools
- Set up the filesystem backend(s):
- In-memory backend:
StateBackend() — ephemeral per-agent-instance (the default for single-message processing)
- Physical filesystem backend: A local-directory backend backed by
config.deep_agent_workspace_path
- Configure permissions to restrict the agent to the workspace directory
- Build the
create_deep_agent with the full harness stack:
from deepagents import create_deep_agent
from deepagents.backends import StateBackend
from deepagents.middleware import FilesystemMiddleware
from deepagents.middleware.subagents import SubAgentMiddleware
from langchain.agents.middleware import TodoListMiddleware
# Get all tools (custom + MCP)
tools = await get_all_tools(...)
backend = StateBackend()
self.agent = create_deep_agent(
model=self.llm,
tools=tools,
system_prompt=self.system_instructions, # existing bot instructions
middleware=[
FilesystemMiddleware(
backend=backend,
# Restrict to workspace directory
# permissions=[...]
),
TodoListMiddleware(),
SubAgentMiddleware(
backend=backend,
subagents=[], # sub-agents can be configured later
),
],
)
4. Backend extensibility — manolo_bot/storage/deep_agent/
Following the project's existing storage pattern (abstract base → concrete implementations), create:
manolo_bot/storage/deep_agent/
├── __init__.py
├── base.py # Abstract backend protocol / base class
├── in_memory.py # In-memory StateBackend (from deepagents)
└── local_fs.py # Local filesystem backend wrapping DEEP_AGENT_WORKSPACE_PATH
The base.py should define the backend interface (wrapping deepagents.backends.StateBackend protocol) so future backends like PostgreSQL or cloud storage are drop-in replacements — similar to how storage/messages/base.py abstracts BaseMessagesStorage.
5. Main entry point — manolo_bot/main.py
Update three things:
a) Import the new class:
from manolo_bot.ai.llmdeepagent import LLMDeepAgent
b) Update instance_llm_bot() for 3-way selection:
if config.ai_mode == "deep_agent":
llm_bot = LLMDeepAgent(...)
elif config.ai_mode == "agent":
llm_bot = LLMAgent(...)
else:
llm_bot = LLMBot(...)
c) System instructions: The deep agent mode should use the same bot character/system instructions as the other modes. The instructions building logic in main.py needs to account for the new mode (e.g., agent_instructions block should apply to both agent and deep_agent modes).
6. Documentation
a) env.example — Add:
# AI Mode Selection: llm, agent (default), deep_agent
AI_MODE=agent
# Deep Agent Workspace (virtual filesystem root)
# DEEP_AGENT_WORKSPACE_PATH=/tmp/manolo_bot/workspace
b) AGENTS.md — Update the Mental Model section to reference all 3 modes.
c) README.md — Document the new deep agent mode, its capabilities, and configuration.
7. Tests
a) Mode selection test — Verify that AI_MODE maps to the correct class:
ai_mode="agent" → LLMAgent
ai_mode="deep_agent" → LLMDeepAgent
ai_mode="llm" → LLMBot
- Backward compat:
AGENT_MODE=True → LLMAgent
b) LLMDeepAgent initialization test — Verify that create_deep_agent is called with the expected middleware stack (at minimum TodoListMiddleware and FilesystemMiddleware should be present).
How the mode selection works
When the bot starts, Config loads AI_MODE from the environment:
# .env
AI_MODE=deep_agent
In main.py, instance_llm_bot() reads config.ai_mode and instantiates the corresponding class:
config.ai_mode = "deep_agent" → LLMDeepAgent(...)
config.ai_mode = "agent" → LLMAgent(...)
config.ai_mode = "llm" → LLMBot(...)
Each instance is still created per message (not persisted across messages). The deep agent's StateBackend is ephemeral (in-memory) per instance, but the filesystem workspace path persists data on disk across runs.
Architecture Diagram (simplified)
main.py
└── instance_llm_bot()
├── ai_mode="llm" → LLMBot → simple LLM + manual tool loop
├── ai_mode="agent" → LLMAgent → create_agent() + auto tool loop
└── ai_mode="deep_agent" → LLMDeepAgent → create_deep_agent() + todo list
+ sub-agents
+ filesystem
LLMDeepAgent extends LLMAgent extends LLMBot. All answer_*_message() methods live in LLMAgent and work unchanged — only initialize_async_resources() is overridden to build the deep agent harness.
Dependencies and versions
| Dependency |
Version |
Notes |
deepagents |
>=0.7 |
Full harness with FilesystemMiddleware, SubAgentMiddleware |
No new LLM or framework dependencies beyond deepagents — the project already has all the LangChain/LangGraph stack needed.
Checklist for the developer
References
Summary
Add a third AI mode to
manolo-botusing LangChain's Deep Agents harness (deepagentspackage). This mode provides planning tools (todo list), sub-agents, and a virtual filesystem, enabling the bot to handle more complex, multi-step tasks than the current agent mode.Currently the project has two modes:
llm:LLMBot— simple LLM with manual tool execution loopagent:LLMAgent— useslangchain.agents.create_agent(LangGraph-based, auto tool loop)The new mode will be
deep_agent:LLMDeepAgent— usesdeepagents.create_deep_agentwith the full harness stack (todo list, sub-agents, virtual filesystem).The default mode will be
agent(notllm).Background — What are Deep Agents?
LangChain Deep Agents is a batteries-included agent harness built on top of
create_agent. It adds four key capabilities:TodoListMiddlewarethat keeps the agent on track for long taskstasktool that spawns isolated sub-agents for focused subtasksStateBackendprotocol)Plus human-in-the-loop steering, context management, and code execution.
Official resources:
Implementation Tasks
1. Add
deepagentsdependencyFile:
pyproject.tomlThen run
uv sync --devto install.2. Configuration —
manolo_bot/config.pyAdd a new
StringFieldfor mode selection:Keep the existing
agent_modeboolean for backwards compatibility. Add logic so that:AI_MODEis explicitly set → use itAGENT_MODE=True→ treat asai_mode="agent"AGENT_MODE=False(or unset) → default toai_mode="agent"(the new default)Also add a config field for the deep agent workspace path (separate from
DOCUMENT_STORAGE_PATHwhich is for temp document handling):3. New module —
manolo_bot/ai/llmdeepagent.pyCreate a new class
LLMDeepAgentthat extendsLLMAgent:Only override
initialize_async_resources(). The parentLLMAgentalready implementsanswer_message(),answer_image_message(),answer_voice_message(),answer_document_message()— they all callself.agent.ainvoke(...)which works the same way for bothcreate_agentandcreate_deep_agent.What
initialize_async_resources()should do:super().initialize_async_resources()to connect MCP and load toolsStateBackend()— ephemeral per-agent-instance (the default for single-message processing)config.deep_agent_workspace_pathcreate_deep_agentwith the full harness stack:4. Backend extensibility —
manolo_bot/storage/deep_agent/Following the project's existing storage pattern (abstract base → concrete implementations), create:
The
base.pyshould define the backend interface (wrappingdeepagents.backends.StateBackendprotocol) so future backends like PostgreSQL or cloud storage are drop-in replacements — similar to howstorage/messages/base.pyabstractsBaseMessagesStorage.5. Main entry point —
manolo_bot/main.pyUpdate three things:
a) Import the new class:
b) Update
instance_llm_bot()for 3-way selection:c) System instructions: The deep agent mode should use the same bot character/system instructions as the other modes. The instructions building logic in
main.pyneeds to account for the new mode (e.g.,agent_instructionsblock should apply to bothagentanddeep_agentmodes).6. Documentation
a)
env.example— Add:b)
AGENTS.md— Update the Mental Model section to reference all 3 modes.c)
README.md— Document the new deep agent mode, its capabilities, and configuration.7. Tests
a) Mode selection test — Verify that
AI_MODEmaps to the correct class:ai_mode="agent"→LLMAgentai_mode="deep_agent"→LLMDeepAgentai_mode="llm"→LLMBotAGENT_MODE=True→LLMAgentb)
LLMDeepAgentinitialization test — Verify thatcreate_deep_agentis called with the expected middleware stack (at minimumTodoListMiddlewareandFilesystemMiddlewareshould be present).How the mode selection works
When the bot starts,
ConfigloadsAI_MODEfrom the environment:In
main.py,instance_llm_bot()readsconfig.ai_modeand instantiates the corresponding class:Each instance is still created per message (not persisted across messages). The deep agent's
StateBackendis ephemeral (in-memory) per instance, but the filesystem workspace path persists data on disk across runs.Architecture Diagram (simplified)
LLMDeepAgentextendsLLMAgentextendsLLMBot. Allanswer_*_message()methods live inLLMAgentand work unchanged — onlyinitialize_async_resources()is overridden to build the deep agent harness.Dependencies and versions
deepagentsNo new LLM or framework dependencies beyond
deepagents— the project already has all the LangChain/LangGraph stack needed.Checklist for the developer
deepagents>=0.7topyproject.tomland runuv sync --devai_modeanddeep_agent_workspace_pathfields tomanolo_bot/config.pyAGENT_MODEboolean →ai_modestring mappingmanolo_bot/ai/llmdeepagent.pywithLLMDeepAgent(LLMAgent)initialize_async_resources()to usecreate_deep_agentStateBackend()+FilesystemMiddlewarewith workspace permissionsTodoListMiddleware()for planningSubAgentMiddleware()for sub-agent supportmanolo_bot/storage/deep_agent/with base backend abstractionmanolo_bot/main.py: import + 3-way mode selection + instructionsenv.examplewith new variables and commentsAGENTS.mdto document all 3 modesREADME.mdwith deep agent mode documentationLLMDeepAgentinitializationuv run python -m unittest discover testsand verify all passgit checkout -b feature/deep-agent-modeReferences