Skip to content

Commit 7f52c21

Browse files
committed
A concept for adding subagent support to deepagents-cli
1 parent 676d455 commit 7f52c21

File tree

1 file changed

+101
-0
lines changed
  • libs/deepagents-cli/deepagents_cli

1 file changed

+101
-0
lines changed

libs/deepagents-cli/deepagents_cli/agent.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
"""Agent management and creation for the CLI."""
22

33
import os
4+
import re
45
import shutil
56
from pathlib import Path
7+
from functools import reduce
8+
from itertools import chain
9+
from typing import Any
610

711
from deepagents import create_deep_agent
812
from deepagents.backends import CompositeBackend
@@ -57,6 +61,14 @@ def list_agents() -> None:
5761
console.print()
5862

5963

64+
def list_assistant_ids() -> list:
65+
agents_dir = settings.user_deepagents_dir
66+
project_deepagents_dir = settings.ensure_project_deepagents_dir()
67+
if not agents_dir.exists() or not any(chain(agents_dir.iterdir(), project_deepagents_dir.iterdir())):
68+
return []
69+
return reduce(lambda agent_paths, agent_path: [*agent_paths, agent_path.name] if agent_path.is_dir() and (agent_path / "agent.md").exists() else agent_paths, sorted(chain(agents_dir.iterdir(), project_deepagents_dir.iterdir())), [])
70+
71+
6072
def reset_agent(agent_name: str, source_agent: str | None = None) -> None:
6173
"""Reset an agent to default or copy from another agent."""
6274
agents_dir = settings.user_deepagents_dir
@@ -322,6 +334,89 @@ def _add_interrupt_on() -> dict[str, InterruptOnConfig]:
322334
"task": task_interrupt_config,
323335
}
324336

337+
def create_subagent_with_config(
338+
model: str | BaseChatModel,
339+
assistant_id: str,
340+
tools: list[BaseTool],
341+
*,
342+
sandbox: SandboxBackendProtocol | None = None,
343+
sandbox_type: str | None = None,
344+
) -> dict[str, Any]:
345+
"""Create and configure an agent with the specified model and tools.
346+
347+
Args:
348+
model: LLM model to use
349+
assistant_id: Agent identifier for memory storage
350+
tools: Additional tools to provide to agent
351+
sandbox: Optional sandbox backend for remote execution (e.g., ModalBackend).
352+
If None, uses local filesystem + shell.
353+
sandbox_type: Type of sandbox provider ("modal", "runloop", "daytona")
354+
355+
Returns:
356+
subagent dict object
357+
"""
358+
# Setup agent directory for persistent memory (same for both local and remote modes)
359+
agent_dir = settings.ensure_agent_dir(assistant_id)
360+
agent_md = agent_dir / "agent.md"
361+
if not agent_md.exists():
362+
source_content = get_default_coding_instructions()
363+
agent_md.write_text(source_content)
364+
365+
# Skills directory - per-agent (user-level)
366+
skills_dir = settings.ensure_user_skills_dir(assistant_id)
367+
368+
# Project-level skills directory (if in a project)
369+
project_skills_dir = settings.get_project_skills_dir()
370+
371+
# CONDITIONAL SETUP: Local vs Remote Sandbox
372+
if sandbox is None:
373+
# Middleware: AgentMemoryMiddleware, SkillsMiddleware, ShellToolMiddleware
374+
agent_middleware = [
375+
AgentMemoryMiddleware(settings=settings, assistant_id=assistant_id),
376+
SkillsMiddleware(
377+
skills_dir=skills_dir,
378+
assistant_id=assistant_id,
379+
project_skills_dir=project_skills_dir,
380+
),
381+
ShellMiddleware(
382+
workspace_root=str(Path.cwd()),
383+
env=os.environ,
384+
),
385+
]
386+
else:
387+
# Middleware: AgentMemoryMiddleware and SkillsMiddleware
388+
# NOTE: File operations (ls, read, write, edit, glob, grep) and execute tool
389+
# are automatically provided by create_deep_agent when backend is a SandboxBackend.
390+
agent_middleware = [
391+
AgentMemoryMiddleware(settings=settings, assistant_id=assistant_id),
392+
SkillsMiddleware(
393+
skills_dir=skills_dir,
394+
assistant_id=assistant_id,
395+
project_skills_dir=project_skills_dir,
396+
),
397+
]
398+
399+
# Get the system prompt (sandbox-aware and with skills)
400+
system_prompt = get_system_prompt(assistant_id=assistant_id, sandbox_type=sandbox_type)
401+
402+
interrupt_on = _add_interrupt_on()
403+
404+
content = agent_md.read_text()
405+
match1 = re.search(r"model: ([^\n]*)", content) # allow subagent to dynamically switch model
406+
model = match1 and match1.group(1) or model
407+
description = content.split("#")[0].replace(f"model: {model}", "").strip()
408+
agent = {
409+
"name": assistant_id,
410+
"description": description,
411+
"model": model,
412+
"system_prompt": system_prompt,
413+
"tools": tools,
414+
"middleware": agent_middleware,
415+
"interrupt_on": interrupt_on,
416+
}
417+
418+
return agent
419+
325420

326421
def create_agent_with_config(
327422
model: str | BaseChatModel,
@@ -404,13 +499,19 @@ def create_agent_with_config(
404499

405500
interrupt_on = _add_interrupt_on()
406501

502+
subagents = []
503+
for assistant_id in [item for item in list_assistant_ids() if item != assistant_id]:
504+
subagent = create_subagent_with_config(model, assistant_id, tools, sandbox=sandbox, sandbox_type=sandbox_type)
505+
subagents.append(subagent)
506+
407507
agent = create_deep_agent(
408508
model=model,
409509
system_prompt=system_prompt,
410510
tools=tools,
411511
backend=composite_backend,
412512
middleware=agent_middleware,
413513
interrupt_on=interrupt_on,
514+
subagents=subagents
414515
).with_config(config)
415516

416517
agent.checkpointer = InMemorySaver()

0 commit comments

Comments
 (0)