Skip to content

Add Deep Agent mode (LLMDeepAgent) using LangChain Deep Agents harness #68

Description

@cccaballero

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:

  1. Detailed system prompt — A general-purpose prompt inspired by Claude Code
  2. Planning tool — A no-op TodoListMiddleware that keeps the agent on track for long tasks
  3. Sub-agents — A task tool that spawns isolated sub-agents for focused subtasks
  4. 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:

  1. Call super().initialize_async_resources() to connect MCP and load tools
  2. 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
  3. Configure permissions to restrict the agent to the workspace directory
  4. 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=TrueLLMAgent

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

  • Add deepagents>=0.7 to pyproject.toml and run uv sync --dev
  • Add ai_mode and deep_agent_workspace_path fields to manolo_bot/config.py
  • Implement backward-compat logic for AGENT_MODE boolean → ai_mode string mapping
  • Create manolo_bot/ai/llmdeepagent.py with LLMDeepAgent(LLMAgent)
  • Override initialize_async_resources() to use create_deep_agent
  • Set up StateBackend() + FilesystemMiddleware with workspace permissions
  • Add TodoListMiddleware() for planning
  • Add SubAgentMiddleware() for sub-agent support
  • Create manolo_bot/storage/deep_agent/ with base backend abstraction
  • Create local filesystem backend implementation
  • Update manolo_bot/main.py: import + 3-way mode selection + instructions
  • Update env.example with new variables and comments
  • Update AGENTS.md to document all 3 modes
  • Update README.md with deep agent mode documentation
  • Write tests for mode selection
  • Write tests for LLMDeepAgent initialization
  • Run uv run python -m unittest discover tests and verify all pass
  • Create feature branch: git checkout -b feature/deep-agent-mode
  • Commit changes with a descriptive message

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions