deepagents 0.2.7__py3-none-any.whl → 0.3.0__py3-none-any.whl

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.
@@ -5,14 +5,87 @@ must follow. Backends can store files in different locations (state, filesystem,
5
5
  database, etc.) and provide a uniform interface for file operations.
6
6
  """
7
7
 
8
+ import abc
9
+ import asyncio
8
10
  from collections.abc import Callable
9
11
  from dataclasses import dataclass
10
- from typing import Any, Protocol, TypeAlias, TypedDict, runtime_checkable
12
+ from typing import Any, Literal, NotRequired, TypeAlias
11
13
 
12
14
  from langchain.tools import ToolRuntime
15
+ from typing_extensions import TypedDict
16
+
17
+ FileOperationError = Literal[
18
+ "file_not_found", # Download: file doesn't exist
19
+ "permission_denied", # Both: access denied
20
+ "is_directory", # Download: tried to download directory as file
21
+ "invalid_path", # Both: path syntax malformed (parent dir missing, invalid chars)
22
+ ]
23
+ """Standardized error codes for file upload/download operations.
24
+
25
+ These represent common, recoverable errors that an LLM can understand and potentially fix:
26
+ - file_not_found: The requested file doesn't exist (download)
27
+ - parent_not_found: The parent directory doesn't exist (upload)
28
+ - permission_denied: Access denied for the operation
29
+ - is_directory: Attempted to download a directory as a file
30
+ - invalid_path: Path syntax is malformed or contains invalid characters
31
+ """
32
+
33
+
34
+ @dataclass
35
+ class FileDownloadResponse:
36
+ """Result of a single file download operation.
37
+
38
+ The response is designed to allow partial success in batch operations.
39
+ The errors are standardized using FileOperationError literals
40
+ for certain recoverable conditions for use cases that involve
41
+ LLMs performing file operations.
42
+
43
+ Attributes:
44
+ path: The file path that was requested. Included for easy correlation
45
+ when processing batch results, especially useful for error messages.
46
+ content: File contents as bytes on success, None on failure.
47
+ error: Standardized error code on failure, None on success.
48
+ Uses FileOperationError literal for structured, LLM-actionable error reporting.
49
+
50
+ Examples:
51
+ >>> # Success
52
+ >>> FileDownloadResponse(path="/app/config.json", content=b"{...}", error=None)
53
+ >>> # Failure
54
+ >>> FileDownloadResponse(path="/wrong/path.txt", content=None, error="file_not_found")
55
+ """
56
+
57
+ path: str
58
+ content: bytes | None = None
59
+ error: FileOperationError | None = None
60
+
13
61
 
62
+ @dataclass
63
+ class FileUploadResponse:
64
+ """Result of a single file upload operation.
65
+
66
+ The response is designed to allow partial success in batch operations.
67
+ The errors are standardized using FileOperationError literals
68
+ for certain recoverable conditions for use cases that involve
69
+ LLMs performing file operations.
70
+
71
+ Attributes:
72
+ path: The file path that was requested. Included for easy correlation
73
+ when processing batch results and for clear error messages.
74
+ error: Standardized error code on failure, None on success.
75
+ Uses FileOperationError literal for structured, LLM-actionable error reporting.
76
+
77
+ Examples:
78
+ >>> # Success
79
+ >>> FileUploadResponse(path="/app/data.txt", error=None)
80
+ >>> # Failure
81
+ >>> FileUploadResponse(path="/readonly/file.txt", error="permission_denied")
82
+ """
14
83
 
15
- class FileInfo(TypedDict, total=False):
84
+ path: str
85
+ error: FileOperationError | None = None
86
+
87
+
88
+ class FileInfo(TypedDict):
16
89
  """Structured file listing info.
17
90
 
18
91
  Minimal contract used across backends. Only "path" is required.
@@ -20,9 +93,9 @@ class FileInfo(TypedDict, total=False):
20
93
  """
21
94
 
22
95
  path: str
23
- is_dir: bool
24
- size: int # bytes (approx)
25
- modified_at: str # ISO timestamp if known
96
+ is_dir: NotRequired[bool]
97
+ size: NotRequired[int] # bytes (approx)
98
+ modified_at: NotRequired[str] # ISO timestamp if known
26
99
 
27
100
 
28
101
  class GrepMatch(TypedDict):
@@ -85,8 +158,7 @@ class EditResult:
85
158
  occurrences: int | None = None
86
159
 
87
160
 
88
- @runtime_checkable
89
- class BackendProtocol(Protocol):
161
+ class BackendProtocol(abc.ABC):
90
162
  """Protocol for pluggable memory backends (single, unified).
91
163
 
92
164
  Backends can store files in different locations (state, filesystem, database, etc.)
@@ -94,15 +166,30 @@ class BackendProtocol(Protocol):
94
166
 
95
167
  All file data is represented as dicts with the following structure:
96
168
  {
97
- "content": list[str], # Lines of text content
98
- "created_at": str, # ISO format timestamp
99
- "modified_at": str, # ISO format timestamp
169
+ "content": list[str], # Lines of text content
170
+ "created_at": str, # ISO format timestamp
171
+ "modified_at": str, # ISO format timestamp
100
172
  }
101
173
  """
102
174
 
103
175
  def ls_info(self, path: str) -> list["FileInfo"]:
104
- """Structured listing with file metadata."""
105
- ...
176
+ """List all files in a directory with metadata.
177
+
178
+ Args:
179
+ path: Absolute path to the directory to list. Must start with '/'.
180
+
181
+ Returns:
182
+ List of FileInfo dicts containing file metadata:
183
+
184
+ - `path` (required): Absolute file path
185
+ - `is_dir` (optional): True if directory
186
+ - `size` (optional): File size in bytes
187
+ - `modified_at` (optional): ISO 8601 timestamp
188
+ """
189
+
190
+ async def als_info(self, path: str) -> list["FileInfo"]:
191
+ """Async version of ls_info."""
192
+ return await asyncio.to_thread(self.ls_info, path)
106
193
 
107
194
  def read(
108
195
  self,
@@ -110,8 +197,35 @@ class BackendProtocol(Protocol):
110
197
  offset: int = 0,
111
198
  limit: int = 2000,
112
199
  ) -> str:
113
- """Read file content with line numbers or an error string."""
114
- ...
200
+ """Read file content with line numbers.
201
+
202
+ Args:
203
+ file_path: Absolute path to the file to read. Must start with '/'.
204
+ offset: Line number to start reading from (0-indexed). Default: 0.
205
+ limit: Maximum number of lines to read. Default: 2000.
206
+
207
+ Returns:
208
+ String containing file content formatted with line numbers (cat -n format),
209
+ starting at line 1. Lines longer than 2000 characters are truncated.
210
+
211
+ Returns an error string if the file doesn't exist or can't be read.
212
+
213
+ !!! note
214
+ - Use pagination (offset/limit) for large files to avoid context overflow
215
+ - First scan: `read(path, limit=100)` to see file structure
216
+ - Read more: `read(path, offset=100, limit=200)` for next section
217
+ - ALWAYS read a file before editing it
218
+ - If file exists but is empty, you'll receive a system reminder warning
219
+ """
220
+
221
+ async def aread(
222
+ self,
223
+ file_path: str,
224
+ offset: int = 0,
225
+ limit: int = 2000,
226
+ ) -> str:
227
+ """Async version of read."""
228
+ return await asyncio.to_thread(self.read, file_path, offset, limit)
115
229
 
116
230
  def grep_raw(
117
231
  self,
@@ -119,20 +233,94 @@ class BackendProtocol(Protocol):
119
233
  path: str | None = None,
120
234
  glob: str | None = None,
121
235
  ) -> list["GrepMatch"] | str:
122
- """Structured search results or error string for invalid input."""
123
- ...
236
+ """Search for a literal text pattern in files.
237
+
238
+ Args:
239
+ pattern: Literal string to search for (NOT regex).
240
+ Performs exact substring matching within file content.
241
+ Example: "TODO" matches any line containing "TODO"
242
+
243
+ path: Optional directory path to search in.
244
+ If None, searches in current working directory.
245
+ Example: "/workspace/src"
246
+
247
+ glob: Optional glob pattern to filter which FILES to search.
248
+ Filters by filename/path, not content.
249
+ Supports standard glob wildcards:
250
+ - `*` matches any characters in filename
251
+ - `**` matches any directories recursively
252
+ - `?` matches single character
253
+ - `[abc]` matches one character from set
254
+
255
+ Examples:
256
+ - "*.py" - only search Python files
257
+ - "**/*.txt" - search all .txt files recursively
258
+ - "src/**/*.js" - search JS files under src/
259
+ - "test[0-9].txt" - search test0.txt, test1.txt, etc.
260
+
261
+ Returns:
262
+ On success: list[GrepMatch] with structured results containing:
263
+ - path: Absolute file path
264
+ - line: Line number (1-indexed)
265
+ - text: Full line content containing the match
266
+
267
+ On error: str with error message (e.g., invalid path, permission denied)
268
+ """
269
+
270
+ async def agrep_raw(
271
+ self,
272
+ pattern: str,
273
+ path: str | None = None,
274
+ glob: str | None = None,
275
+ ) -> list["GrepMatch"] | str:
276
+ """Async version of grep_raw."""
277
+ return await asyncio.to_thread(self.grep_raw, pattern, path, glob)
124
278
 
125
279
  def glob_info(self, pattern: str, path: str = "/") -> list["FileInfo"]:
126
- """Structured glob matching returning FileInfo dicts."""
127
- ...
280
+ """Find files matching a glob pattern.
281
+
282
+ Args:
283
+ pattern: Glob pattern with wildcards to match file paths.
284
+ Supports standard glob syntax:
285
+ - `*` matches any characters within a filename/directory
286
+ - `**` matches any directories recursively
287
+ - `?` matches a single character
288
+ - `[abc]` matches one character from set
289
+
290
+ path: Base directory to search from. Default: "/" (root).
291
+ The pattern is applied relative to this path.
292
+
293
+ Returns:
294
+ list of FileInfo
295
+ """
296
+
297
+ async def aglob_info(self, pattern: str, path: str = "/") -> list["FileInfo"]:
298
+ """Async version of glob_info."""
299
+ return await asyncio.to_thread(self.glob_info, pattern, path)
128
300
 
129
301
  def write(
130
302
  self,
131
303
  file_path: str,
132
304
  content: str,
133
305
  ) -> WriteResult:
134
- """Create a new file. Returns WriteResult; error populated on failure."""
135
- ...
306
+ """Write content to a new file in the filesystem, error if file exists.
307
+
308
+ Args:
309
+ file_path: Absolute path where the file should be created.
310
+ Must start with '/'.
311
+ content: String content to write to the file.
312
+
313
+ Returns:
314
+ WriteResult
315
+ """
316
+
317
+ async def awrite(
318
+ self,
319
+ file_path: str,
320
+ content: str,
321
+ ) -> WriteResult:
322
+ """Async version of write."""
323
+ return await asyncio.to_thread(self.write, file_path, content)
136
324
 
137
325
  def edit(
138
326
  self,
@@ -141,8 +329,78 @@ class BackendProtocol(Protocol):
141
329
  new_string: str,
142
330
  replace_all: bool = False,
143
331
  ) -> EditResult:
144
- """Edit a file by replacing string occurrences. Returns EditResult."""
145
- ...
332
+ """Perform exact string replacements in an existing file.
333
+
334
+ Args:
335
+ file_path: Absolute path to the file to edit. Must start with '/'.
336
+ old_string: Exact string to search for and replace.
337
+ Must match exactly including whitespace and indentation.
338
+ new_string: String to replace old_string with.
339
+ Must be different from old_string.
340
+ replace_all: If True, replace all occurrences. If False (default),
341
+ old_string must be unique in the file or the edit fails.
342
+
343
+ Returns:
344
+ EditResult
345
+ """
346
+
347
+ async def aedit(
348
+ self,
349
+ file_path: str,
350
+ old_string: str,
351
+ new_string: str,
352
+ replace_all: bool = False,
353
+ ) -> EditResult:
354
+ """Async version of edit."""
355
+ return await asyncio.to_thread(self.edit, file_path, old_string, new_string, replace_all)
356
+
357
+ def upload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]:
358
+ """Upload multiple files to the sandbox.
359
+
360
+ This API is designed to allow developers to use it either directly or
361
+ by exposing it to LLMs via custom tools.
362
+
363
+ Args:
364
+ files: List of (path, content) tuples to upload.
365
+
366
+ Returns:
367
+ List of FileUploadResponse objects, one per input file.
368
+ Response order matches input order (response[i] for files[i]).
369
+ Check the error field to determine success/failure per file.
370
+
371
+ Examples:
372
+ ```python
373
+ responses = sandbox.upload_files(
374
+ [
375
+ ("/app/config.json", b"{...}"),
376
+ ("/app/data.txt", b"content"),
377
+ ]
378
+ )
379
+ ```
380
+ """
381
+
382
+ async def aupload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]:
383
+ """Async version of upload_files."""
384
+ return await asyncio.to_thread(self.upload_files, files)
385
+
386
+ def download_files(self, paths: list[str]) -> list[FileDownloadResponse]:
387
+ """Download multiple files from the sandbox.
388
+
389
+ This API is designed to allow developers to use it either directly or
390
+ by exposing it to LLMs via custom tools.
391
+
392
+ Args:
393
+ paths: List of file paths to download.
394
+
395
+ Returns:
396
+ List of FileDownloadResponse objects, one per input path.
397
+ Response order matches input order (response[i] for paths[i]).
398
+ Check the error field to determine success/failure per file.
399
+ """
400
+
401
+ async def adownload_files(self, paths: list[str]) -> list[FileDownloadResponse]:
402
+ """Async version of download_files."""
403
+ return await asyncio.to_thread(self.download_files, paths)
146
404
 
147
405
 
148
406
  @dataclass
@@ -162,8 +420,7 @@ class ExecuteResponse:
162
420
  """Whether the output was truncated due to backend limitations."""
163
421
 
164
422
 
165
- @runtime_checkable
166
- class SandboxBackendProtocol(BackendProtocol, Protocol):
423
+ class SandboxBackendProtocol(BackendProtocol):
167
424
  """Protocol for sandboxed backends with isolated runtime.
168
425
 
169
426
  Sandboxed backends run in isolated environments (e.g., separate processes,
@@ -184,12 +441,17 @@ class SandboxBackendProtocol(BackendProtocol, Protocol):
184
441
  Returns:
185
442
  ExecuteResponse with combined output, exit code, optional signal, and truncation flag.
186
443
  """
187
- ...
444
+
445
+ async def aexecute(
446
+ self,
447
+ command: str,
448
+ ) -> ExecuteResponse:
449
+ """Async version of execute."""
450
+ return await asyncio.to_thread(self.execute, command)
188
451
 
189
452
  @property
190
453
  def id(self) -> str:
191
- """Unique identifier for the sandbox backend."""
192
- ...
454
+ """Unique identifier for the sandbox backend instance."""
193
455
 
194
456
 
195
457
  BackendFactory: TypeAlias = Callable[[ToolRuntime], BackendProtocol]
@@ -9,12 +9,15 @@ from __future__ import annotations
9
9
 
10
10
  import base64
11
11
  import json
12
+ import shlex
12
13
  from abc import ABC, abstractmethod
13
14
 
14
15
  from deepagents.backends.protocol import (
15
16
  EditResult,
16
17
  ExecuteResponse,
18
+ FileDownloadResponse,
17
19
  FileInfo,
20
+ FileUploadResponse,
18
21
  GrepMatch,
19
22
  SandboxBackendProtocol,
20
23
  WriteResult,
@@ -270,10 +273,10 @@ except PermissionError:
270
273
  glob: str | None = None,
271
274
  ) -> list[GrepMatch] | str:
272
275
  """Structured search results or error string for invalid input."""
273
- search_path = path or "."
276
+ search_path = shlex.quote(path or ".")
274
277
 
275
278
  # Build grep command to get structured output
276
- grep_opts = "-rHn" # recursive, with filename, with line number
279
+ grep_opts = "-rHnF" # recursive, with filename, with line number, fixed-strings (literal)
277
280
 
278
281
  # Add glob pattern if specified
279
282
  glob_pattern = ""
@@ -281,9 +284,9 @@ except PermissionError:
281
284
  glob_pattern = f"--include='{glob}'"
282
285
 
283
286
  # Escape pattern for shell
284
- pattern_escaped = pattern.replace("'", "'\\\\''")
287
+ pattern_escaped = shlex.quote(pattern)
285
288
 
286
- cmd = f"grep {grep_opts} {glob_pattern} -e '{pattern_escaped}' '{search_path}' 2>/dev/null || true"
289
+ cmd = f"grep {grep_opts} {glob_pattern} -e {pattern_escaped} {search_path} 2>/dev/null || true"
287
290
  result = self.execute(cmd)
288
291
 
289
292
  output = result.output.rstrip()
@@ -338,4 +341,20 @@ except PermissionError:
338
341
  @property
339
342
  @abstractmethod
340
343
  def id(self) -> str:
341
- """Unique identifier for this backend instance."""
344
+ """Unique identifier for the sandbox backend."""
345
+
346
+ @abstractmethod
347
+ def upload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]:
348
+ """Upload multiple files to the sandbox.
349
+
350
+ Implementations must support partial success - catch exceptions per-file
351
+ and return errors in FileUploadResponse objects rather than raising.
352
+ """
353
+
354
+ @abstractmethod
355
+ def download_files(self, paths: list[str]) -> list[FileDownloadResponse]:
356
+ """Download multiple files from the sandbox.
357
+
358
+ Implementations must support partial success - catch exceptions per-file
359
+ and return errors in FileDownloadResponse objects rather than raising.
360
+ """
@@ -90,8 +90,6 @@ class StateBackend(BackendProtocol):
90
90
  infos.sort(key=lambda x: x.get("path", ""))
91
91
  return infos
92
92
 
93
- # Removed legacy ls() convenience to keep lean surface
94
-
95
93
  def read(
96
94
  self,
97
95
  file_path: str,
@@ -101,9 +99,11 @@ class StateBackend(BackendProtocol):
101
99
  """Read file content with line numbers.
102
100
 
103
101
  Args:
104
- file_path: Absolute file path
105
- offset: Line offset to start reading from (0-indexed)
106
- limit: Maximum number of lines to readReturns:
102
+ file_path: Absolute file path.
103
+ offset: Line offset to start reading from (0-indexed).
104
+ limit: Maximum number of lines to read.
105
+
106
+ Returns:
107
107
  Formatted file content with line numbers, or error message.
108
108
  """
109
109
  files = self.runtime.state.get("files", {})
@@ -156,8 +156,6 @@ class StateBackend(BackendProtocol):
156
156
  new_file_data = update_file_data(file_data, new_content)
157
157
  return EditResult(path=file_path, files_update={file_path: new_file_data}, occurrences=int(occurrences))
158
158
 
159
- # Removed legacy grep() convenience to keep lean surface
160
-
161
159
  def grep_raw(
162
160
  self,
163
161
  pattern: str,
@@ -168,6 +166,7 @@ class StateBackend(BackendProtocol):
168
166
  return grep_matches_from_files(files, pattern, path, glob)
169
167
 
170
168
  def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]:
169
+ """Get FileInfo for files matching glob pattern."""
171
170
  files = self.runtime.state.get("files", {})
172
171
  result = _glob_search_files(files, pattern, path)
173
172
  if result == "No files found":
@@ -186,6 +185,3 @@ class StateBackend(BackendProtocol):
186
185
  }
187
186
  )
188
187
  return infos
189
-
190
-
191
- # Provider classes removed: prefer callables like `lambda rt: StateBackend(rt)`
@@ -5,7 +5,15 @@ from typing import Any
5
5
  from langgraph.config import get_config
6
6
  from langgraph.store.base import BaseStore, Item
7
7
 
8
- from deepagents.backends.protocol import BackendProtocol, EditResult, FileInfo, GrepMatch, WriteResult
8
+ from deepagents.backends.protocol import (
9
+ BackendProtocol,
10
+ EditResult,
11
+ FileDownloadResponse,
12
+ FileInfo,
13
+ FileUploadResponse,
14
+ GrepMatch,
15
+ WriteResult,
16
+ )
9
17
  from deepagents.backends.utils import (
10
18
  _glob_search_files,
11
19
  create_file_data,
@@ -30,17 +38,18 @@ class StoreBackend(BackendProtocol):
30
38
  """Initialize StoreBackend with runtime.
31
39
 
32
40
  Args:
41
+ runtime: The ToolRuntime instance providing store access and configuration.
33
42
  """
34
43
  self.runtime = runtime
35
44
 
36
45
  def _get_store(self) -> BaseStore:
37
46
  """Get the store instance.
38
47
 
39
- Args:Returns:
40
- BaseStore instance
48
+ Returns:
49
+ BaseStore instance from the runtime.
41
50
 
42
51
  Raises:
43
- ValueError: If no store is available or runtime not provided
52
+ ValueError: If no store is available in the runtime.
44
53
  """
45
54
  store = self.runtime.store
46
55
  if store is None:
@@ -240,8 +249,6 @@ class StoreBackend(BackendProtocol):
240
249
  infos.sort(key=lambda x: x.get("path", ""))
241
250
  return infos
242
251
 
243
- # Removed legacy ls() convenience to keep lean surface
244
-
245
252
  def read(
246
253
  self,
247
254
  file_path: str,
@@ -251,8 +258,9 @@ class StoreBackend(BackendProtocol):
251
258
  """Read file content with line numbers.
252
259
 
253
260
  Args:
254
- file_path: Absolute file path
255
- offset: Line offset to start reading from (0-indexed)limit: Maximum number of lines to read
261
+ file_path: Absolute file path.
262
+ offset: Line offset to start reading from (0-indexed).
263
+ limit: Maximum number of lines to read.
256
264
 
257
265
  Returns:
258
266
  Formatted file content with line numbers, or error message.
@@ -376,3 +384,59 @@ class StoreBackend(BackendProtocol):
376
384
  }
377
385
  )
378
386
  return infos
387
+
388
+ def upload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]:
389
+ """Upload multiple files to the store.
390
+
391
+ Args:
392
+ files: List of (path, content) tuples where content is bytes.
393
+
394
+ Returns:
395
+ List of FileUploadResponse objects, one per input file.
396
+ Response order matches input order.
397
+ """
398
+ store = self._get_store()
399
+ namespace = self._get_namespace()
400
+ responses: list[FileUploadResponse] = []
401
+
402
+ for path, content in files:
403
+ content_str = content.decode("utf-8")
404
+ # Create file data
405
+ file_data = create_file_data(content_str)
406
+ store_value = self._convert_file_data_to_store_value(file_data)
407
+
408
+ # Store the file
409
+ store.put(namespace, path, store_value)
410
+ responses.append(FileUploadResponse(path=path, error=None))
411
+
412
+ return responses
413
+
414
+ def download_files(self, paths: list[str]) -> list[FileDownloadResponse]:
415
+ """Download multiple files from the store.
416
+
417
+ Args:
418
+ paths: List of file paths to download.
419
+
420
+ Returns:
421
+ List of FileDownloadResponse objects, one per input path.
422
+ Response order matches input order.
423
+ """
424
+ store = self._get_store()
425
+ namespace = self._get_namespace()
426
+ responses: list[FileDownloadResponse] = []
427
+
428
+ for path in paths:
429
+ item = store.get(namespace, path)
430
+
431
+ if item is None:
432
+ responses.append(FileDownloadResponse(path=path, content=None, error="file_not_found"))
433
+ continue
434
+
435
+ file_data = self._convert_store_item_to_file_data(item)
436
+ # Convert file data to bytes
437
+ content_str = file_data_to_string(file_data)
438
+ content_bytes = content_str.encode("utf-8")
439
+
440
+ responses.append(FileDownloadResponse(path=path, content=content_bytes, error=None))
441
+
442
+ return responses
deepagents/graph.py CHANGED
@@ -98,6 +98,18 @@ def create_deep_agent(
98
98
  if model is None:
99
99
  model = get_default_model()
100
100
 
101
+ if (
102
+ model.profile is not None
103
+ and isinstance(model.profile, dict)
104
+ and "max_input_tokens" in model.profile
105
+ and isinstance(model.profile["max_input_tokens"], int)
106
+ ):
107
+ trigger = ("fraction", 0.85)
108
+ keep = ("fraction", 0.10)
109
+ else:
110
+ trigger = ("tokens", 170000)
111
+ keep = ("messages", 6)
112
+
101
113
  deepagent_middleware = [
102
114
  TodoListMiddleware(),
103
115
  FilesystemMiddleware(backend=backend),
@@ -110,8 +122,9 @@ def create_deep_agent(
110
122
  FilesystemMiddleware(backend=backend),
111
123
  SummarizationMiddleware(
112
124
  model=model,
113
- max_tokens_before_summary=170000,
114
- messages_to_keep=6,
125
+ trigger=trigger,
126
+ keep=keep,
127
+ trim_tokens_to_summarize=None,
115
128
  ),
116
129
  AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"),
117
130
  PatchToolCallsMiddleware(),
@@ -121,8 +134,9 @@ def create_deep_agent(
121
134
  ),
122
135
  SummarizationMiddleware(
123
136
  model=model,
124
- max_tokens_before_summary=170000,
125
- messages_to_keep=6,
137
+ trigger=trigger,
138
+ keep=keep,
139
+ trim_tokens_to_summarize=None,
126
140
  ),
127
141
  AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"),
128
142
  PatchToolCallsMiddleware(),