deepagents 0.3.0__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. {deepagents-0.3.0 → deepagents-0.3.2}/PKG-INFO +6 -5
  2. {deepagents-0.3.0 → deepagents-0.3.2}/README.md +3 -3
  3. {deepagents-0.3.0 → deepagents-0.3.2}/deepagents/__init__.py +9 -1
  4. {deepagents-0.3.0 → deepagents-0.3.2}/deepagents/backends/composite.py +136 -46
  5. {deepagents-0.3.0 → deepagents-0.3.2}/deepagents/backends/state.py +50 -1
  6. {deepagents-0.3.0 → deepagents-0.3.2}/deepagents/graph.py +68 -29
  7. {deepagents-0.3.0 → deepagents-0.3.2}/deepagents/middleware/__init__.py +4 -0
  8. deepagents-0.3.2/deepagents/middleware/memory.py +369 -0
  9. deepagents-0.3.2/deepagents/middleware/skills.py +695 -0
  10. {deepagents-0.3.0 → deepagents-0.3.2}/deepagents/middleware/subagents.py +12 -5
  11. {deepagents-0.3.0 → deepagents-0.3.2}/deepagents.egg-info/PKG-INFO +6 -5
  12. {deepagents-0.3.0 → deepagents-0.3.2}/deepagents.egg-info/SOURCES.txt +2 -0
  13. {deepagents-0.3.0 → deepagents-0.3.2}/deepagents.egg-info/requires.txt +2 -1
  14. {deepagents-0.3.0 → deepagents-0.3.2}/pyproject.toml +3 -2
  15. {deepagents-0.3.0 → deepagents-0.3.2}/deepagents/backends/__init__.py +0 -0
  16. {deepagents-0.3.0 → deepagents-0.3.2}/deepagents/backends/filesystem.py +0 -0
  17. {deepagents-0.3.0 → deepagents-0.3.2}/deepagents/backends/protocol.py +0 -0
  18. {deepagents-0.3.0 → deepagents-0.3.2}/deepagents/backends/sandbox.py +0 -0
  19. {deepagents-0.3.0 → deepagents-0.3.2}/deepagents/backends/store.py +0 -0
  20. {deepagents-0.3.0 → deepagents-0.3.2}/deepagents/backends/utils.py +0 -0
  21. {deepagents-0.3.0 → deepagents-0.3.2}/deepagents/middleware/filesystem.py +0 -0
  22. {deepagents-0.3.0 → deepagents-0.3.2}/deepagents/middleware/patch_tool_calls.py +0 -0
  23. {deepagents-0.3.0 → deepagents-0.3.2}/deepagents.egg-info/dependency_links.txt +0 -0
  24. {deepagents-0.3.0 → deepagents-0.3.2}/deepagents.egg-info/top_level.txt +0 -0
  25. {deepagents-0.3.0 → deepagents-0.3.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepagents
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: General purpose 'deep agent' with sub-agent spawning, todo list capabilities, and mock file system. Built on LangGraph.
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://docs.langchain.com/oss/python/deepagents/overview
@@ -12,8 +12,9 @@ Project-URL: Reddit, https://www.reddit.com/r/LangChain/
12
12
  Requires-Python: <4.0,>=3.11
13
13
  Description-Content-Type: text/markdown
14
14
  Requires-Dist: langchain-anthropic<2.0.0,>=1.2.0
15
+ Requires-Dist: langchain-google-genai
15
16
  Requires-Dist: langchain<2.0.0,>=1.1.0
16
- Requires-Dist: langchain-core<2.0.0,>=1.1.0
17
+ Requires-Dist: langchain-core<2.0.0,>=1.2.5
17
18
  Requires-Dist: wcmatch
18
19
 
19
20
  # 🧠🤖Deep Agents
@@ -218,7 +219,7 @@ A main feature of Deep Agents is their ability to spawn subagents. You can speci
218
219
  class SubAgent(TypedDict):
219
220
  name: str
220
221
  description: str
221
- prompt: str
222
+ system_prompt: str
222
223
  tools: Sequence[BaseTool | Callable | dict[str, Any]]
223
224
  model: NotRequired[str | BaseChatModel]
224
225
  middleware: NotRequired[list[AgentMiddleware]]
@@ -233,7 +234,7 @@ class CompiledSubAgent(TypedDict):
233
234
  **SubAgent fields:**
234
235
  - **name**: This is the name of the subagent, and how the main agent will call the subagent
235
236
  - **description**: This is the description of the subagent that is shown to the main agent
236
- - **prompt**: This is the prompt used for the subagent
237
+ - **system_prompt**: This is the system prompt used for the subagent
237
238
  - **tools**: This is the list of tools that the subagent has access to.
238
239
  - **model**: Optional model name or model instance.
239
240
  - **middleware** Additional middleware to attach to the subagent. See [here](https://docs.langchain.com/oss/python/langchain/middleware) for an introduction into middleware and how it works with create_agent.
@@ -484,7 +485,7 @@ Prior versions of deepagents separated sync and async agent factories.
484
485
 
485
486
  The `deepagents` library can be ran with MCP tools. This can be achieved by using the [Langchain MCP Adapter library](https://github.com/langchain-ai/langchain-mcp-adapters).
486
487
 
487
- **NOTE:** You will want to use `from deepagents import async_create_deep_agent` to use the async version of `deepagents`, since MCP tools are async
488
+ **NOTE:** MCP tools are async, so you'll need to use `agent.ainvoke()` or `agent.astream()` for invocation.
488
489
 
489
490
  (To run the example below, will need to `pip install langchain-mcp-adapters`)
490
491
 
@@ -200,7 +200,7 @@ A main feature of Deep Agents is their ability to spawn subagents. You can speci
200
200
  class SubAgent(TypedDict):
201
201
  name: str
202
202
  description: str
203
- prompt: str
203
+ system_prompt: str
204
204
  tools: Sequence[BaseTool | Callable | dict[str, Any]]
205
205
  model: NotRequired[str | BaseChatModel]
206
206
  middleware: NotRequired[list[AgentMiddleware]]
@@ -215,7 +215,7 @@ class CompiledSubAgent(TypedDict):
215
215
  **SubAgent fields:**
216
216
  - **name**: This is the name of the subagent, and how the main agent will call the subagent
217
217
  - **description**: This is the description of the subagent that is shown to the main agent
218
- - **prompt**: This is the prompt used for the subagent
218
+ - **system_prompt**: This is the system prompt used for the subagent
219
219
  - **tools**: This is the list of tools that the subagent has access to.
220
220
  - **model**: Optional model name or model instance.
221
221
  - **middleware** Additional middleware to attach to the subagent. See [here](https://docs.langchain.com/oss/python/langchain/middleware) for an introduction into middleware and how it works with create_agent.
@@ -466,7 +466,7 @@ Prior versions of deepagents separated sync and async agent factories.
466
466
 
467
467
  The `deepagents` library can be ran with MCP tools. This can be achieved by using the [Langchain MCP Adapter library](https://github.com/langchain-ai/langchain-mcp-adapters).
468
468
 
469
- **NOTE:** You will want to use `from deepagents import async_create_deep_agent` to use the async version of `deepagents`, since MCP tools are async
469
+ **NOTE:** MCP tools are async, so you'll need to use `agent.ainvoke()` or `agent.astream()` for invocation.
470
470
 
471
471
  (To run the example below, will need to `pip install langchain-mcp-adapters`)
472
472
 
@@ -2,6 +2,14 @@
2
2
 
3
3
  from deepagents.graph import create_deep_agent
4
4
  from deepagents.middleware.filesystem import FilesystemMiddleware
5
+ from deepagents.middleware.memory import MemoryMiddleware
5
6
  from deepagents.middleware.subagents import CompiledSubAgent, SubAgent, SubAgentMiddleware
6
7
 
7
- __all__ = ["CompiledSubAgent", "FilesystemMiddleware", "SubAgent", "SubAgentMiddleware", "create_deep_agent"]
8
+ __all__ = [
9
+ "CompiledSubAgent",
10
+ "FilesystemMiddleware",
11
+ "MemoryMiddleware",
12
+ "SubAgent",
13
+ "SubAgentMiddleware",
14
+ "create_deep_agent",
15
+ ]
@@ -1,4 +1,22 @@
1
- """CompositeBackend: Route operations to different backends based on path prefix."""
1
+ """Composite backend that routes file operations by path prefix.
2
+
3
+ Routes operations to different backends based on path prefixes. Use this when you
4
+ need different storage strategies for different paths (e.g., state for temp files,
5
+ persistent store for memories).
6
+
7
+ Examples:
8
+ ```python
9
+ from deepagents.backends.composite import CompositeBackend
10
+ from deepagents.backends.state import StateBackend
11
+ from deepagents.backends.store import StoreBackend
12
+
13
+ runtime = make_runtime()
14
+ composite = CompositeBackend(default=StateBackend(runtime), routes={"/memories/": StoreBackend(runtime)})
15
+
16
+ composite.write("/temp.txt", "ephemeral")
17
+ composite.write("/memories/note.md", "persistent")
18
+ ```
19
+ """
2
20
 
3
21
  from collections import defaultdict
4
22
 
@@ -16,12 +34,38 @@ from deepagents.backends.protocol import (
16
34
  from deepagents.backends.state import StateBackend
17
35
 
18
36
 
19
- class CompositeBackend:
37
+ class CompositeBackend(BackendProtocol):
38
+ """Routes file operations to different backends by path prefix.
39
+
40
+ Matches paths against route prefixes (longest first) and delegates to the
41
+ corresponding backend. Unmatched paths use the default backend.
42
+
43
+ Attributes:
44
+ default: Backend for paths that don't match any route.
45
+ routes: Map of path prefixes to backends (e.g., {"/memories/": store_backend}).
46
+ sorted_routes: Routes sorted by length (longest first) for correct matching.
47
+
48
+ Examples:
49
+ ```python
50
+ composite = CompositeBackend(default=StateBackend(runtime), routes={"/memories/": StoreBackend(runtime), "/cache/": StoreBackend(runtime)})
51
+
52
+ composite.write("/temp.txt", "data")
53
+ composite.write("/memories/note.txt", "data")
54
+ ```
55
+ """
56
+
20
57
  def __init__(
21
58
  self,
22
59
  default: BackendProtocol | StateBackend,
23
60
  routes: dict[str, BackendProtocol],
24
61
  ) -> None:
62
+ """Initialize composite backend.
63
+
64
+ Args:
65
+ default: Backend for paths that don't match any route.
66
+ routes: Map of path prefixes to backends. Prefixes must start with "/"
67
+ and should end with "/" (e.g., "/memories/").
68
+ """
25
69
  # Default backend
26
70
  self.default = default
27
71
 
@@ -32,14 +76,14 @@ class CompositeBackend:
32
76
  self.sorted_routes = sorted(routes.items(), key=lambda x: len(x[0]), reverse=True)
33
77
 
34
78
  def _get_backend_and_key(self, key: str) -> tuple[BackendProtocol, str]:
35
- """Determine which backend handles this key and strip prefix.
79
+ """Get backend for path and strip route prefix.
36
80
 
37
81
  Args:
38
- key: Original file path
82
+ key: File path to route.
39
83
 
40
84
  Returns:
41
- Tuple of (backend, stripped_key) where stripped_key has the route
42
- prefix removed (but keeps leading slash).
85
+ Tuple of (backend, stripped_path). The stripped path has the route
86
+ prefix removed but keeps the leading slash.
43
87
  """
44
88
  # Check routes in order of length (longest first)
45
89
  for prefix, backend in self.sorted_routes:
@@ -53,14 +97,23 @@ class CompositeBackend:
53
97
  return self.default, key
54
98
 
55
99
  def ls_info(self, path: str) -> list[FileInfo]:
56
- """List files and directories in the specified directory (non-recursive).
100
+ """List directory contents (non-recursive).
101
+
102
+ If path matches a route, lists only that backend. If path is "/", aggregates
103
+ default backend plus virtual route directories. Otherwise lists default backend.
57
104
 
58
105
  Args:
59
- path: Absolute path to directory.
106
+ path: Absolute directory path starting with "/".
60
107
 
61
108
  Returns:
62
- List of FileInfo-like dicts with route prefixes added, for files and directories directly in the directory.
63
- Directories have a trailing / in their path and is_dir=True.
109
+ List of FileInfo dicts. Directories have trailing "/" and is_dir=True.
110
+ Route prefixes are restored in returned paths.
111
+
112
+ Examples:
113
+ ```python
114
+ infos = composite.ls_info("/")
115
+ infos = composite.ls_info("/memories/")
116
+ ```
64
117
  """
65
118
  # Check if path matches a specific route
66
119
  for route_prefix, backend in self.sorted_routes:
@@ -169,6 +222,28 @@ class CompositeBackend:
169
222
  path: str | None = None,
170
223
  glob: str | None = None,
171
224
  ) -> list[GrepMatch] | str:
225
+ """Search files for regex pattern.
226
+
227
+ Routes to backends based on path: specific route searches one backend,
228
+ "/" or None searches all backends, otherwise searches default backend.
229
+
230
+ Args:
231
+ pattern: Regex pattern to search for.
232
+ path: Directory to search. None searches all backends.
233
+ glob: Glob pattern to filter files (e.g., "*.py", "**/*.txt").
234
+ Filters by filename, not content.
235
+
236
+ Returns:
237
+ List of GrepMatch dicts with path (route prefix restored), line
238
+ (1-indexed), and text. Returns error string on failure.
239
+
240
+ Examples:
241
+ ```python
242
+ matches = composite.grep_raw("TODO", path="/memories/")
243
+ matches = composite.grep_raw("error", path="/")
244
+ matches = composite.grep_raw("import", path="/", glob="*.py")
245
+ ```
246
+ """
172
247
  # If path targets a specific route, search only that backend
173
248
  for route_prefix, backend in self.sorted_routes:
174
249
  if path is not None and path.startswith(route_prefix.rstrip("/")):
@@ -178,22 +253,26 @@ class CompositeBackend:
178
253
  return raw
179
254
  return [{**m, "path": f"{route_prefix[:-1]}{m['path']}"} for m in raw]
180
255
 
181
- # Otherwise, search default and all routed backends and merge
182
- all_matches: list[GrepMatch] = []
183
- raw_default = self.default.grep_raw(pattern, path, glob) # type: ignore[attr-defined]
184
- if isinstance(raw_default, str):
185
- # This happens if error occurs
186
- return raw_default
187
- all_matches.extend(raw_default)
188
-
189
- for route_prefix, backend in self.routes.items():
190
- raw = backend.grep_raw(pattern, "/", glob)
191
- if isinstance(raw, str):
256
+ # If path is None or "/", search default and all routed backends and merge
257
+ # Otherwise, search only the default backend
258
+ if path is None or path == "/":
259
+ all_matches: list[GrepMatch] = []
260
+ raw_default = self.default.grep_raw(pattern, path, glob) # type: ignore[attr-defined]
261
+ if isinstance(raw_default, str):
192
262
  # This happens if error occurs
193
- return raw
194
- all_matches.extend({**m, "path": f"{route_prefix[:-1]}{m['path']}"} for m in raw)
263
+ return raw_default
264
+ all_matches.extend(raw_default)
195
265
 
196
- return all_matches
266
+ for route_prefix, backend in self.routes.items():
267
+ raw = backend.grep_raw(pattern, "/", glob)
268
+ if isinstance(raw, str):
269
+ # This happens if error occurs
270
+ return raw
271
+ all_matches.extend({**m, "path": f"{route_prefix[:-1]}{m['path']}"} for m in raw)
272
+
273
+ return all_matches
274
+ # Path specified but doesn't match a route - search only default
275
+ return self.default.grep_raw(pattern, path, glob) # type: ignore[attr-defined]
197
276
 
198
277
  async def agrep_raw(
199
278
  self,
@@ -201,7 +280,10 @@ class CompositeBackend:
201
280
  path: str | None = None,
202
281
  glob: str | None = None,
203
282
  ) -> list[GrepMatch] | str:
204
- """Async version of grep_raw."""
283
+ """Async version of grep_raw.
284
+
285
+ See grep_raw() for detailed documentation on routing behavior and parameters.
286
+ """
205
287
  # If path targets a specific route, search only that backend
206
288
  for route_prefix, backend in self.sorted_routes:
207
289
  if path is not None and path.startswith(route_prefix.rstrip("/")):
@@ -211,22 +293,26 @@ class CompositeBackend:
211
293
  return raw
212
294
  return [{**m, "path": f"{route_prefix[:-1]}{m['path']}"} for m in raw]
213
295
 
214
- # Otherwise, search default and all routed backends and merge
215
- all_matches: list[GrepMatch] = []
216
- raw_default = await self.default.agrep_raw(pattern, path, glob) # type: ignore[attr-defined]
217
- if isinstance(raw_default, str):
218
- # This happens if error occurs
219
- return raw_default
220
- all_matches.extend(raw_default)
221
-
222
- for route_prefix, backend in self.routes.items():
223
- raw = await backend.agrep_raw(pattern, "/", glob)
224
- if isinstance(raw, str):
296
+ # If path is None or "/", search default and all routed backends and merge
297
+ # Otherwise, search only the default backend
298
+ if path is None or path == "/":
299
+ all_matches: list[GrepMatch] = []
300
+ raw_default = await self.default.agrep_raw(pattern, path, glob) # type: ignore[attr-defined]
301
+ if isinstance(raw_default, str):
225
302
  # This happens if error occurs
226
- return raw
227
- all_matches.extend({**m, "path": f"{route_prefix[:-1]}{m['path']}"} for m in raw)
303
+ return raw_default
304
+ all_matches.extend(raw_default)
305
+
306
+ for route_prefix, backend in self.routes.items():
307
+ raw = await backend.agrep_raw(pattern, "/", glob)
308
+ if isinstance(raw, str):
309
+ # This happens if error occurs
310
+ return raw
311
+ all_matches.extend({**m, "path": f"{route_prefix[:-1]}{m['path']}"} for m in raw)
228
312
 
229
- return all_matches
313
+ return all_matches
314
+ # Path specified but doesn't match a route - search only default
315
+ return await self.default.agrep_raw(pattern, path, glob) # type: ignore[attr-defined]
230
316
 
231
317
  def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]:
232
318
  results: list[FileInfo] = []
@@ -379,19 +465,23 @@ class CompositeBackend:
379
465
  self,
380
466
  command: str,
381
467
  ) -> ExecuteResponse:
382
- """Execute a command via the default backend.
383
-
384
- Execution is not path-specific, so it always delegates to the default backend.
385
- The default backend must implement SandboxBackendProtocol for this to work.
468
+ """Execute shell command via default backend.
386
469
 
387
470
  Args:
388
- command: Full shell command string to execute.
471
+ command: Shell command to execute.
389
472
 
390
473
  Returns:
391
- ExecuteResponse with combined output, exit code, and truncation flag.
474
+ ExecuteResponse with output, exit code, and truncation flag.
392
475
 
393
476
  Raises:
394
- NotImplementedError: If default backend doesn't support execution.
477
+ NotImplementedError: If default backend doesn't implement SandboxBackendProtocol.
478
+
479
+ Examples:
480
+ ```python
481
+ composite = CompositeBackend(default=FilesystemBackend(root_dir="/tmp"), routes={"/memories/": StoreBackend(runtime)})
482
+
483
+ result = composite.execute("ls -la")
484
+ ```
395
485
  """
396
486
  if isinstance(self.default, SandboxBackendProtocol):
397
487
  return self.default.execute(command)
@@ -2,7 +2,15 @@
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
- from deepagents.backends.protocol import BackendProtocol, EditResult, FileInfo, GrepMatch, WriteResult
5
+ from deepagents.backends.protocol import (
6
+ BackendProtocol,
7
+ EditResult,
8
+ FileDownloadResponse,
9
+ FileInfo,
10
+ FileUploadResponse,
11
+ GrepMatch,
12
+ WriteResult,
13
+ )
6
14
  from deepagents.backends.utils import (
7
15
  _glob_search_files,
8
16
  create_file_data,
@@ -185,3 +193,44 @@ class StateBackend(BackendProtocol):
185
193
  }
186
194
  )
187
195
  return infos
196
+
197
+ def upload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]:
198
+ """Upload multiple files to state.
199
+
200
+ Args:
201
+ files: List of (path, content) tuples to upload
202
+
203
+ Returns:
204
+ List of FileUploadResponse objects, one per input file
205
+ """
206
+ raise NotImplementedError(
207
+ "StateBackend does not support upload_files yet. You can upload files "
208
+ "directly by passing them in invoke if you're storing files in the memory."
209
+ )
210
+
211
+ def download_files(self, paths: list[str]) -> list[FileDownloadResponse]:
212
+ """Download multiple files from state.
213
+
214
+ Args:
215
+ paths: List of file paths to download
216
+
217
+ Returns:
218
+ List of FileDownloadResponse objects, one per input path
219
+ """
220
+ state_files = self.runtime.state.get("files", {})
221
+ responses: list[FileDownloadResponse] = []
222
+
223
+ for path in paths:
224
+ file_data = state_files.get(path)
225
+
226
+ if file_data is None:
227
+ responses.append(FileDownloadResponse(path=path, content=None, error="file_not_found"))
228
+ continue
229
+
230
+ # Convert file data to bytes
231
+ content_str = file_data_to_string(file_data)
232
+ content_bytes = content_str.encode("utf-8")
233
+
234
+ responses.append(FileDownloadResponse(path=path, content=content_bytes, error=None))
235
+
236
+ return responses
@@ -8,6 +8,7 @@ from langchain.agents.middleware import HumanInTheLoopMiddleware, InterruptOnCon
8
8
  from langchain.agents.middleware.summarization import SummarizationMiddleware
9
9
  from langchain.agents.middleware.types import AgentMiddleware
10
10
  from langchain.agents.structured_output import ResponseFormat
11
+ from langchain.chat_models import init_chat_model
11
12
  from langchain_anthropic import ChatAnthropic
12
13
  from langchain_anthropic.middleware import AnthropicPromptCachingMiddleware
13
14
  from langchain_core.language_models import BaseChatModel
@@ -17,9 +18,12 @@ from langgraph.graph.state import CompiledStateGraph
17
18
  from langgraph.store.base import BaseStore
18
19
  from langgraph.types import Checkpointer
19
20
 
21
+ from deepagents.backends import StateBackend
20
22
  from deepagents.backends.protocol import BackendFactory, BackendProtocol
21
23
  from deepagents.middleware.filesystem import FilesystemMiddleware
24
+ from deepagents.middleware.memory import MemoryMiddleware
22
25
  from deepagents.middleware.patch_tool_calls import PatchToolCallsMiddleware
26
+ from deepagents.middleware.skills import SkillsMiddleware
23
27
  from deepagents.middleware.subagents import CompiledSubAgent, SubAgent, SubAgentMiddleware
24
28
 
25
29
  BASE_AGENT_PROMPT = "In order to complete the objective that the user asks of you, you have access to a number of standard tools."
@@ -44,6 +48,8 @@ def create_deep_agent(
44
48
  system_prompt: str | None = None,
45
49
  middleware: Sequence[AgentMiddleware] = (),
46
50
  subagents: list[SubAgent | CompiledSubAgent] | None = None,
51
+ skills: list[str] | None = None,
52
+ memory: list[str] | None = None,
47
53
  response_format: ResponseFormat | None = None,
48
54
  context_schema: type[Any] | None = None,
49
55
  checkpointer: Checkpointer | None = None,
@@ -79,6 +85,16 @@ def create_deep_agent(
79
85
  - (optional) `model` (either a LanguageModelLike instance or dict
80
86
  settings)
81
87
  - (optional) `middleware` (list of AgentMiddleware)
88
+ skills: Optional list of skill source paths (e.g., ["/skills/user/", "/skills/project/"]).
89
+ Paths must be specified using POSIX conventions (forward slashes) and are relative
90
+ to the backend's root. When using StateBackend (default), provide skill files via
91
+ `invoke(files={...})`. With FilesystemBackend, skills are loaded from disk relative
92
+ to the backend's root_dir. Later sources override earlier ones for skills with the
93
+ same name (last one wins).
94
+ memory: Optional list of memory file paths (AGENTS.md files) to load
95
+ (e.g., ["/memory/AGENTS.md"]). Display names
96
+ are automatically derived from paths. Memory is loaded at agent startup and
97
+ added into the system prompt.
82
98
  response_format: A structured output response format to use for the agent.
83
99
  context_schema: The schema of the deep agent.
84
100
  checkpointer: Optional checkpointer for persisting agent state between runs.
@@ -97,6 +113,8 @@ def create_deep_agent(
97
113
  """
98
114
  if model is None:
99
115
  model = get_default_model()
116
+ elif isinstance(model, str):
117
+ model = init_chat_model(model)
100
118
 
101
119
  if (
102
120
  model.profile is not None
@@ -110,37 +128,58 @@ def create_deep_agent(
110
128
  trigger = ("tokens", 170000)
111
129
  keep = ("messages", 6)
112
130
 
113
- deepagent_middleware = [
131
+ # Build middleware stack for subagents (includes skills if provided)
132
+ subagent_middleware: list[AgentMiddleware] = [
114
133
  TodoListMiddleware(),
115
- FilesystemMiddleware(backend=backend),
116
- SubAgentMiddleware(
117
- default_model=model,
118
- default_tools=tools,
119
- subagents=subagents if subagents is not None else [],
120
- default_middleware=[
121
- TodoListMiddleware(),
122
- FilesystemMiddleware(backend=backend),
123
- SummarizationMiddleware(
124
- model=model,
125
- trigger=trigger,
126
- keep=keep,
127
- trim_tokens_to_summarize=None,
128
- ),
129
- AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"),
130
- PatchToolCallsMiddleware(),
131
- ],
132
- default_interrupt_on=interrupt_on,
133
- general_purpose_agent=True,
134
- ),
135
- SummarizationMiddleware(
136
- model=model,
137
- trigger=trigger,
138
- keep=keep,
139
- trim_tokens_to_summarize=None,
140
- ),
141
- AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"),
142
- PatchToolCallsMiddleware(),
143
134
  ]
135
+
136
+ backend = backend if backend is not None else (lambda rt: StateBackend(rt))
137
+
138
+ if skills is not None:
139
+ subagent_middleware.append(SkillsMiddleware(backend=backend, sources=skills))
140
+ subagent_middleware.extend(
141
+ [
142
+ FilesystemMiddleware(backend=backend),
143
+ SummarizationMiddleware(
144
+ model=model,
145
+ trigger=trigger,
146
+ keep=keep,
147
+ trim_tokens_to_summarize=None,
148
+ ),
149
+ AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"),
150
+ PatchToolCallsMiddleware(),
151
+ ]
152
+ )
153
+
154
+ # Build main agent middleware stack
155
+ deepagent_middleware: list[AgentMiddleware] = [
156
+ TodoListMiddleware(),
157
+ ]
158
+ if memory is not None:
159
+ deepagent_middleware.append(MemoryMiddleware(backend=backend, sources=memory))
160
+ if skills is not None:
161
+ deepagent_middleware.append(SkillsMiddleware(backend=backend, sources=skills))
162
+ deepagent_middleware.extend(
163
+ [
164
+ FilesystemMiddleware(backend=backend),
165
+ SubAgentMiddleware(
166
+ default_model=model,
167
+ default_tools=tools,
168
+ subagents=subagents if subagents is not None else [],
169
+ default_middleware=subagent_middleware,
170
+ default_interrupt_on=interrupt_on,
171
+ general_purpose_agent=True,
172
+ ),
173
+ SummarizationMiddleware(
174
+ model=model,
175
+ trigger=trigger,
176
+ keep=keep,
177
+ trim_tokens_to_summarize=None,
178
+ ),
179
+ AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"),
180
+ PatchToolCallsMiddleware(),
181
+ ]
182
+ )
144
183
  if middleware:
145
184
  deepagent_middleware.extend(middleware)
146
185
  if interrupt_on is not None:
@@ -1,11 +1,15 @@
1
1
  """Middleware for the DeepAgent."""
2
2
 
3
3
  from deepagents.middleware.filesystem import FilesystemMiddleware
4
+ from deepagents.middleware.memory import MemoryMiddleware
5
+ from deepagents.middleware.skills import SkillsMiddleware
4
6
  from deepagents.middleware.subagents import CompiledSubAgent, SubAgent, SubAgentMiddleware
5
7
 
6
8
  __all__ = [
7
9
  "CompiledSubAgent",
8
10
  "FilesystemMiddleware",
11
+ "MemoryMiddleware",
12
+ "SkillsMiddleware",
9
13
  "SubAgent",
10
14
  "SubAgentMiddleware",
11
15
  ]