feat(sdk): delete dir support in delete tool - #3851
Conversation
There was a problem hiding this comment.
Security Issues
- Recursive delete permission bypass via empty directory omission
The recursive delete authorization scan inFilesystemMiddleware._delete_denied_path()(and its async counterpart_adelete_denied_path()) enumerates subtree contents exclusively throughbackend.glob("**/*", path=target).matches. TheFilesystemBackend.glob()implementation explicitly skips non-file entries (if not is_file: continue), meaning empty directories are never returned._scan_delete_for_denied_path()then derives intermediate directories only from the file paths returned — so any empty directory in the subtree, including one that is the direct target of adeny writepermission rule, is never checked. An attacker-controlled LLM agent can issuedelete("/allowed/parent"), the pre-check passes (glob returns no files to check), andshutil.rmtree()then silently removes every empty denied directory under/allowed/parent.
Eugene Yurtsev (eyurtsev)
left a comment
There was a problem hiding this comment.
Strategy makes sense, let's pull the function to out of the class namespace and add exhaustive unit tests to check for the various edge cases
There was a problem hiding this comment.
Security Issues
- Uncontrolled Resource Consumption
The new recursiveStoreBackend.delete()path (and its async twinadelete()) calls_search_store_paginated()/_asearch_store_paginated(), which accumulates every page of the entire namespace into an unbounded in-memory list before filtering by the requested prefix. These helpers have no maximum item/byte cap and no timeout guard. Because thedeletetool is exposed as a LangGraph tool throughFilesystemMiddleware— callable by any agent that receives user input — an attacker (or a prompt-injected agent) can triggerdelete("/")or a similarly broad path and force the server to load the entire namespace into memory before attempting the deletions. The async variant is equally affected. All other methods that call_search_store_paginated()(e.g.grep,glob) share the same unbounded-scan pattern, butdeleteadditionally issues a single massivebatch()call proportional to the number of keys found.
Recommendation
- Avoid full namespace scans for delete. Use key-prefix queries if the store backend supports them, or stream pages with strict maximum item/byte limits and delete in bounded batches.
- Reject overly broad recursive deletes (e.g.
/) at the schema or validation layer unless explicitly approved by a permission rule. - Add a hard cap (e.g. 10 000 items) inside
_search_store_paginated()/_asearch_store_paginated()so that all callers are protected.
|
|
||
| if store.get(namespace, file_path) is None: | ||
| items = self._search_store_paginated(store, namespace) | ||
| to_delete = expand_delete_keys((str(item.key) for item in items), file_path) |
There was a problem hiding this comment.
The recursive delete path loads the entire namespace before filtering keys for the requested prefix:
items = self._search_store_paginated(store, namespace)
to_delete = expand_delete_keys((str(item.key) for item in items), file_path)_search_store_paginated() (store.py:376) accumulates every page into all_items: list[Item] with no maximum item or byte cap. For a namespace with N items this runs ceil(N/100) round-trips to the store and allocates O(N) heap objects before a single key is deleted. The subsequent store.batch([PutOp(..., None) for key in to_delete]) then issues all N deletions in one unbounded request with no chunking or back-pressure.
The delete tool is exposed as a LangGraph StructuredTool callable by any agent that processes user messages. validate_path() accepts / as a valid path (it only blocks traversal sequences), so an attacker — or a prompt-injected agent — can call delete("/") to trigger a full-namespace scan and wipe. No rate-limiting, no max-items guard, and no timeout exist at any layer in the delete path. The async variant (adelete(), store.py:742) is identically affected.
Remediation:
- Add a hard cap (e.g.
MAX_SCAN_ITEMS = 10_000) inside_search_store_paginated()and_asearch_store_paginated()that raises an exception if the accumulator exceeds the limit — this protects all callers, not just delete. - For delete specifically, use a prefix-aware store query (pass a
filter/querytostore.search()) so only matching keys are fetched rather than the entire namespace. - Chunk
to_deleteinto bounded batches (e.g. ≤ 500 keys perstore.batch()call) with optional yield points for async. - Reject delete paths of
/or other root-level broad paths at the schema orvalidate_path()layer unless an explicit permission rule approves them.
Attack Path
-
Attacker sends an HTTP POST to the LangGraph server (e.g.
/threads/{thread_id}/runs) with a user message instructing the agent to delete all files, e.g."Delete everything under /"or via a prompt-injection payload in agent-readable content. -
The LangGraph agent calls the
deletetool withfile_path="/". -
FilesystemMiddlewaredelete wrapper (filesystem.py:1303/1342) callsvalidate_path("/")— passes (no traversal sequences). -
_find_delete_deny_patterns()returns empty list (no deny rules configured by default); execution continues. -
StoreBackend.delete("/")(store.py:717) calls_search_store_paginated(store, namespace)(store.py:734), entering an unbounded loop that fetches all N items in pages of 100 and accumulates them in memory. -
expand_delete_keys(...)returns every key (all start with/). -
store.batch([PutOp(namespace, key, None) for key in to_delete])deletes all N keys in one request (store.py:739). -
Attack can be repeated concurrently across threads to compound CPU/memory exhaustion.
For more details, see the finding in Corridor.
Provide feedback: Reply with whether this is a valid vulnerability or false positive to help improve Corridor's accuracy.
There was a problem hiding this comment.
Optimizations that we can follow up with:
-
Make sure that we have async implementations for grep, glob, ls (agrep, aglob, alas)
-
Make sure that we don't eagerly load more information than we need. Pagination is OK but we don't need to load all pages into memory for things like grep, glob, ls
-
When not needed we can work with keys only for glob and ls we only need the key not the value. (although full optimization would require changing how the store works in langgraph, and it's likely not that much of a high priority right now.)
-
We want to be able to terminate glob/grep/ls early if there are too many results (this is not just for StoreBackend, but for all backends and would require modifying the schema of grep,glob and ls return type to accommodate some information indicating that that the result was truncated due to too many matches. (And ask the agent in the tool message to narrow down its search). cc Sydney Runkle (@sydney-runkle)
| return DeleteResult(error=f"Error: File '{file_path}' not found") | ||
|
|
||
| store.delete(namespace, file_path) | ||
| store.batch([PutOp(namespace, key, None) for key in to_delete]) |
There was a problem hiding this comment.
None -> value=None
And i'd document that this does a deletion?
Combines the v0.7-only delete support work originally merged through: - langchain-ai#3659 - langchain-ai#3691 - langchain-ai#3765 - langchain-ai#3851 Adds a `delete` filesystem tool backed by the backend protocol and supported by state, store, filesystem, sandbox, context-hub, and composite backends. The tool deletes files or directories recursively, and prefix-style backends remove the exact key plus nested descendants. Filesystem permissions treat `delete` as a write operation. Because recursive deletes can affect descendants, deny and interrupt checks use bulk path overlap instead of exact-path matching. BREAKING CHANGE: Agents now see a destructive `delete` filesystem tool when the backend supports it. Existing agents may choose to remove files or directories recursively, and existing filesystem permission rules that allow writes can now authorize deletion unless a narrower deny or interrupt rule blocks the target subtree.
### Description **Depends on**: langchain-ai/deepagents [#3659](langchain-ai/deepagents#3659), [#3691](langchain-ai/deepagents#3691,) [#3851](langchain-ai/deepagents#3851) Updates the built-in tool lists and tables across the Deep Agents docs (overview, permissions, backends, sandboxes, context engineering, CLI overview, and frontend sandbox pattern), and renames `delete_file` to `delete` in the Python human-in-the-loop examples. JS-specific content is intentionally left unchanged, as the deepagents JS SDK does not yet support the delete tool. Python-only code fences are used so these changes appear only in the Python documentation. ### Test Plan - [ ] `vale --glob='!**/node_modules/**' <changed files>` passes with 0 errors/warnings - [ ] `markdownlint --config .markdownlint.json <changed files>` shows no new violations vs. `main` _Opened collaboratively by Nishitha Madhu and open-swe_ --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
> [!CAUTION] > Merging this PR will automatically publish to **PyPI** and create a **GitHub release**. For the full release process, see [`.github/RELEASING.md`](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md). --- _Release notes preview: keep this section in sync with the package `CHANGELOG.md`. Publish reads the merged CHANGELOG via `release.yml`, not this PR description — keep them aligned anyway so the PR stays an accurate historical record for reviewers and anyone returning later._ --- ## [0.7.0](deepagents==0.6.12...deepagents==0.7.0) (2026-07-29) See [the docs](https://docs.langchain.com/oss/python/releases/changelog#deepagents-v0-7-0) for curated release notes. ### ⚠ BREAKING CHANGES * `create_deep_agent` no longer includes `TodoListMiddleware` by default, the `write_todos` tool, `todos` state channel, and todo-planning prompt are now absent. Pass `middleware=[TodoListMiddleware()]` to restore them on the main agent; add it to each `SubAgent`'s middleware to restore them there. ([#4929](#4929)) ([9340518](9340518)) * Default agent prompts are now lean: the authored base prompt is empty, and tool-usage prose that duplicates tool schemas is trimmed. `BASE_AGENT_PROMPT` is deprecated (removal in `deepagents==0.9.0`) but remains importable and still returns the previous authored prompt verbatim; pass it as `create_deep_agent(system_prompt=BASE_AGENT_PROMPT)` to restore the old behavior. ([#4859](#4859)) ([#4979](#4979)) ([a8d1b32](a8d1b32)) ([d9f54fc](d9f54fc)) * The built-in tool-usage prompt constants `TASK_SYSTEM_PROMPT`, `ASYNC_TASK_SYSTEM_PROMPT`, `SUMMARIZATION_SYSTEM_PROMPT`, `FILESYSTEM_SYSTEM_PROMPT`, and `EXECUTION_SYSTEM_PROMPT` are removed, and the `system_prompt` default on `SubAgentMiddleware`, `AsyncSubAgentMiddleware`, `SummarizationToolMiddleware`, and `create_summarization_tool_middleware` is now `None`, which injects no prose. Pass your own string to restore prompt text. ([#4859](#4859)) ([a8d1b32](a8d1b32)) * `FilesystemBackend` and `LocalShellBackend` now default to `virtual_mode=True`. Filesystem paths are anchored under `root_dir`, `..` traversal is rejected, and paths resolving outside `root_dir` raise `ValueError`. Previously an unspecified `virtual_mode` emitted a deprecation warning and fell back to `False`, where absolute host paths were used as-is and `..` could escape `root_dir`. Pass `virtual_mode=False` explicitly to restore the old filesystem behavior. ([#4541](#4541)) ([540a0fa](540a0fa)) * Agents now see a destructive, recursive `delete` filesystem tool whenever the backend supports it, and filesystem permissions classify `delete` as a write operation — so an existing rule allowing writes to a path also authorizes recursively deleting that subtree unless a narrower deny or interrupt rule covers the target. Because recursive deletes affect descendants, deny and interrupt checks use bulk path overlap instead of exact-path matching. To keep the previous behavior, add a deny or interrupt rule, or omit `delete` from `FilesystemMiddleware(tools=...)`. Missing paths return a not-found error, `CompositeBackend` reports an unsupported-operation error when a routed sub-backend cannot delete, and the tool is hidden from the model entirely when the backend itself does not implement it. ([#3659](#3659)) ([#3691](#3691)) ([#3765](#3765)) ([#3851](#3851)) ([f2a21ec](f2a21ec)) * `write_file` can now create a file if it is missing and replaces it entirely if it already exists, instead of returning a file-exists error. The `write_file` tool description no longer requires reading the file first. There is no "create-only" compatibility mode. Workflows, prompts, tests, or guardrails that relied on the file-exists error to force `edit_file` usage or to protect existing content must omit `write_file`, add explicit permission or interrupt rules, or use `edit_file` where preserving existing content matters. ([#4109](#4109)) ([2506fcc](2506fcc)) * Removed deprecated backend compatibility shims. Callers must pass concrete `BackendProtocol` instances (not factories), configure `StoreBackend` with an explicit `namespace`, and use the current `ls` / `glob` / `grep` / `ReadResult` APIs. ([#4541](#4541)) ([540a0fa](540a0fa)) * The deprecated `files_update` attribute and constructor keyword are removed from `WriteResult` and `EditResult`. Custom backends must stop passing `files_update=`, and callers must stop reading `result.files_update`; state writes are emitted directly by `StateBackend`. ([#4541](#4541)) ([540a0fa](540a0fa)) * Removed the deprecated `BackendProtocol` methods `ls_info`, `als_info`, `glob_info`, `aglob_info`, `grep_raw`, and `agrep_raw`. Use `ls` / `glob` / `grep` and their async counterparts. ([#4541](#4541)) ([540a0fa](540a0fa)) * `SummarizationMiddleware(history_path_prefix=...)` was removed and now raises `TypeError`. Configure `CompositeBackend(artifacts_root=...)` instead. ([#4541](#4541)) ([540a0fa](540a0fa)) * Agent-facing `ls` and `glob` tool output now renders empty results as `No files found` instead of `[]`; direct backend APIs continue to return structured empty `LsResult` and `GlobResult` values. Callers that parse tool output should update those checks. ([#3709](#3709)) ([efafd1e](efafd1e)) * `read_file` no longer renders raw text with a fixed-width `cat -n`-style line-number gutter and tab separator. Line and continuation markers are dynamically aligned and separated from source content by two spaces, and the `LINE_NUMBER_WIDTH` constant is removed from `deepagents.backends.utils` and `deepagents.middleware.filesystem`. Callers that parse raw tool output should update those parsers. ([#4561](#4561)) ([cf057b4](cf057b4)) ### Features * Custom middleware passed to `create_deep_agent(..., middleware=[...])` can replace a default middleware instance when `.name` matches, so defaults such as `SummarizationMiddleware` can be overridden without also excluding the built-in instance. ([#4251](#4251)) ([90c8472](90c8472)) * `FilesystemMiddleware(tools=[...])` accepts a keyword-only allowlist of built-in filesystem tools, typed by the newly exported `FsToolName` literal (`"ls"`, `"read_file"`, `"write_file"`, `"edit_file"`, `"delete"`, `"glob"`, `"grep"`, `"execute"`); pass `"all"` or omit the argument to keep every tool. A list must include `"read_file"` or the constructor raises `ValueError`. Omitted built-in tools are non-executable, and custom user tools are unaffected. ([#4325](#4325)) ([#4698](#4698)) ([704a70d](704a70d)) ([9709525](9709525)) * Shorten LLM-facing descriptions for the `task` tool and filesystem tools (`read_file`, `grep`, `edit_file`, `glob`, `execute`). ([#5009](#5009)) ([761f5f0](761f5f0)) * `GrepResult` and `GlobResult` now carry a `truncated` flag so supporting backends can return valid partial results when a match cap or backend deadline is reached; agent-facing tool output adds a note telling the model to narrow the search. `FilesystemBackend` returns partial `grep` and `glob` results on its backend timeout rather than erroring, while other backend or middleware timeouts may still return errors. Its `glob` also gains brace expansion such as `*.{py,md}` (already supported by the state and store backends). ([#4063](#4063)) ([ef591e7](ef591e7)) * The agent-facing `grep` match cap is configurable: `FilesystemMiddleware(grep_max_count=...)` sets the default (`1000`; `None` disables it) and the model can override it per call through the tool's new `max_count` argument. `grep` / `agrep` on `BackendProtocol` and all built-in backends accept a keyword-only `max_count`. Local ripgrep output is streamed and terminated once the cap is reached. Direct `FilesystemBackend.grep()` callers can request surrounding lines with keyword-only `context_lines`. ([#4570](#4570)) ([#4706](#4706)) ([8e86f5e](8e86f5e)) ([65230df](65230df)) * Paginated built-in `read_file` responses report the returned source-line range and next `offset`; total and remaining line counts are included when the backend knows the file length. Resume offsets remain safe when sandbox or middleware limits shorten the visible page. ([#4540](#4540)) ([8321194](8321194)) * Optional video frame extraction for `read_file`, enabled by the new `deepagents[video]` extra. Video files are sampled into JPEG frames, with `offset` and `limit` interpreted as seconds. Without the extra, existing generic video/file content-block behavior remains. ([#4094](#4094)) ([b927147](b927147)) * `FilesystemMiddleware` can capture oversized `execute` tool output directly inside the sandbox artifact path on compatible, opted-in `BaseSandbox` implementations to reduce round trips; `LangSmithSandbox` opts in by default. ([#4230](#4230)) ([02f5bd7](02f5bd7)) * Automatically enable Fireworks prompt-cache session affinity when a compatible `langchain-fireworks` installation is available. ([#4598](#4598)) ([5d878bf](5d878bf)) * Add a built-in NVIDIA Nemotron 3 Ultra harness profile and NVIDIA NIM app-origin attribution. ([#4192](#4192)) ([#4455](#4455)) ([d5a60ec](d5a60ec)) ([4cb4749](4cb4749)) * `RubricMiddleware` now accepts any positive `max_iterations` cap instead of enforcing a hard upper bound. ([#4405](#4405)) ([d6692a7](d6692a7)) ### Bug Fixes * Keep fields marked with `PrivateStateAttr`, including fields declared through `create_deep_agent(state_schema=...)`, out of subagent inputs and returned parent-state updates. ([#4587](#4587)) ([a4662c0](a4662c0)) * Preserve `ContextT` through the `create_deep_agent(..., middleware=[...])` type annotation so type checkers accept context-aware middleware when a matching `context_schema` is passed. ([#4055](#4055)) ([7be76c7](7be76c7)) * Accept YAML list values as well as comma-separated strings for skill `allowed-tools` frontmatter, and make skill truncation warnings actionable with field name, path, length, configured limit, and impact. ([#4140](#4140)) ([#4141](#4141)) ([d62534c](d62534c)) ([2f5f5b8](2f5f5b8)) * Align filesystem instructions with the tools that remain after allowlist and backend-capability filtering, so agents no longer reference hidden `grep`/`glob` tools or prohibit equivalent shell search when dedicated search tools are unavailable. ([#4920](#4920)) ([#4921](#4921)) ([d3650c7](d3650c7)) ([b65cc00](b65cc00)) * Propagate default-backend failures from `CompositeBackend.ls("/")` and `CompositeBackend.als("/")` instead of returning successful route-only listings. ([#4925](#4925)) ([4c3b166](4c3b166)) * Correct `CompositeBackend.glob` / `CompositeBackend.aglob` routing so explicit default-backend paths such as `/tools` do not also return files from routed backends such as `/memories`. ([#4531](#4531)) ([cbdb0a7](cbdb0a7)) * Propagate default- and routed-backend failures from root `CompositeBackend.glob(..., path=None)` / `aglob(..., path=None)` and `path="/"` searches instead of returning incomplete successful results. ([#4063](#4063)) ([ef591e7](ef591e7)) * Constrain sandbox `glob` and slash-pattern `grep` searches to their declared search root by treating leading `/` as search-root-relative, rejecting `..` traversal segments, and filtering symlink-resolved matches outside the root. ([#4588](#4588)) ([c6c7213](c6c7213)) * Unify `grep(..., glob=...)` include-glob semantics across filesystem and in-memory backends: basename patterns like `*.py` match at any depth, and slash-containing patterns like `src/**/*.py` match relative paths consistently. ([#3936](#3936)) ([feab6e0](feab6e0)) * Improve agent-facing `grep` descriptions and no-match hints to steer regex-looking patterns toward literal searches, route slash-containing sandbox include-globs correctly, and shorten default search timeouts so bad patterns and huge trees return guidance faster. ([#4168](#4168)) ([b1dbf5e](b1dbf5e)) * Align sandbox delete behavior with other backends by returning not-found errors for missing paths, and avoid over-blocking unrelated sibling deletes when deny rules use glob patterns. ([#4321](#4321)) ([d77496b](d77496b)) * Improve rubric grader failure diagnostics with configured model, structured-output strategy, and integer HTTP status when available. ([#4938](#4938)) ([#4967](#4967)) ([f51d3a0](f51d3a0)) ([bca70aa](bca70aa)) * Emit `max_iterations_reached` as the terminal `RubricMiddleware` status when the iteration cap is exhausted, instead of a final `needs_revision` event that will not loop. ([#4406](#4406)) ([a51c8d2](a51c8d2)) * Handle missing async subagent URLs consistently in `check_async_task` and `cancel_async_task`. ([#3967](#3967)) ([b0d92c0](b0d92c0)) ### Performance Improvements * Run LangSmith sandbox commands over the async client. ([#5061](#5061)) ([0d08747](0d08747)) --- _End release notes preview._ --- > [!NOTE] > A **New Contributors** section is appended to the GitHub release notes automatically at publish time (see [Release Pipeline](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md#release-pipeline), step 2). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Mason Daugherty <github@mdrxy.com>
Combines the v0.7-only delete support work originally merged through: - langchain-ai#3659 - langchain-ai#3691 - langchain-ai#3765 - langchain-ai#3851 Adds a `delete` filesystem tool backed by the backend protocol and supported by state, store, filesystem, sandbox, context-hub, and composite backends. The tool deletes files or directories recursively, and prefix-style backends remove the exact key plus nested descendants. Filesystem permissions treat `delete` as a write operation. Because recursive deletes can affect descendants, deny and interrupt checks use bulk path overlap instead of exact-path matching. BREAKING CHANGE: Agents now see a destructive `delete` filesystem tool when the backend supports it. Existing agents may choose to remove files or directories recursively, and existing filesystem permission rules that allow writes can now authorize deletion unless a narrower deny or interrupt rule blocks the target subtree.
> [!CAUTION] > Merging this PR will automatically publish to **PyPI** and create a **GitHub release**. For the full release process, see [`.github/RELEASING.md`](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md). --- _Release notes preview: keep this section in sync with the package `CHANGELOG.md`. Publish reads the merged CHANGELOG via `release.yml`, not this PR description — keep them aligned anyway so the PR stays an accurate historical record for reviewers and anyone returning later._ --- ## [0.7.0](langchain-ai/deepagents@deepagents==0.6.12...deepagents==0.7.0) (2026-07-29) See [the docs](https://docs.langchain.com/oss/python/releases/changelog#deepagents-v0-7-0) for curated release notes. ### ⚠ BREAKING CHANGES * `create_deep_agent` no longer includes `TodoListMiddleware` by default, the `write_todos` tool, `todos` state channel, and todo-planning prompt are now absent. Pass `middleware=[TodoListMiddleware()]` to restore them on the main agent; add it to each `SubAgent`'s middleware to restore them there. ([langchain-ai#4929](langchain-ai#4929)) ([9340518](langchain-ai@9340518)) * Default agent prompts are now lean: the authored base prompt is empty, and tool-usage prose that duplicates tool schemas is trimmed. `BASE_AGENT_PROMPT` is deprecated (removal in `deepagents==0.9.0`) but remains importable and still returns the previous authored prompt verbatim; pass it as `create_deep_agent(system_prompt=BASE_AGENT_PROMPT)` to restore the old behavior. ([langchain-ai#4859](langchain-ai#4859)) ([langchain-ai#4979](langchain-ai#4979)) ([a8d1b32](langchain-ai@a8d1b32)) ([d9f54fc](langchain-ai@d9f54fc)) * The built-in tool-usage prompt constants `TASK_SYSTEM_PROMPT`, `ASYNC_TASK_SYSTEM_PROMPT`, `SUMMARIZATION_SYSTEM_PROMPT`, `FILESYSTEM_SYSTEM_PROMPT`, and `EXECUTION_SYSTEM_PROMPT` are removed, and the `system_prompt` default on `SubAgentMiddleware`, `AsyncSubAgentMiddleware`, `SummarizationToolMiddleware`, and `create_summarization_tool_middleware` is now `None`, which injects no prose. Pass your own string to restore prompt text. ([langchain-ai#4859](langchain-ai#4859)) ([a8d1b32](langchain-ai@a8d1b32)) * `FilesystemBackend` and `LocalShellBackend` now default to `virtual_mode=True`. Filesystem paths are anchored under `root_dir`, `..` traversal is rejected, and paths resolving outside `root_dir` raise `ValueError`. Previously an unspecified `virtual_mode` emitted a deprecation warning and fell back to `False`, where absolute host paths were used as-is and `..` could escape `root_dir`. Pass `virtual_mode=False` explicitly to restore the old filesystem behavior. ([langchain-ai#4541](langchain-ai#4541)) ([540a0fa](langchain-ai@540a0fa)) * Agents now see a destructive, recursive `delete` filesystem tool whenever the backend supports it, and filesystem permissions classify `delete` as a write operation — so an existing rule allowing writes to a path also authorizes recursively deleting that subtree unless a narrower deny or interrupt rule covers the target. Because recursive deletes affect descendants, deny and interrupt checks use bulk path overlap instead of exact-path matching. To keep the previous behavior, add a deny or interrupt rule, or omit `delete` from `FilesystemMiddleware(tools=...)`. Missing paths return a not-found error, `CompositeBackend` reports an unsupported-operation error when a routed sub-backend cannot delete, and the tool is hidden from the model entirely when the backend itself does not implement it. ([langchain-ai#3659](langchain-ai#3659)) ([langchain-ai#3691](langchain-ai#3691)) ([langchain-ai#3765](langchain-ai#3765)) ([langchain-ai#3851](langchain-ai#3851)) ([f2a21ec](langchain-ai@f2a21ec)) * `write_file` can now create a file if it is missing and replaces it entirely if it already exists, instead of returning a file-exists error. The `write_file` tool description no longer requires reading the file first. There is no "create-only" compatibility mode. Workflows, prompts, tests, or guardrails that relied on the file-exists error to force `edit_file` usage or to protect existing content must omit `write_file`, add explicit permission or interrupt rules, or use `edit_file` where preserving existing content matters. ([langchain-ai#4109](langchain-ai#4109)) ([2506fcc](langchain-ai@2506fcc)) * Removed deprecated backend compatibility shims. Callers must pass concrete `BackendProtocol` instances (not factories), configure `StoreBackend` with an explicit `namespace`, and use the current `ls` / `glob` / `grep` / `ReadResult` APIs. ([langchain-ai#4541](langchain-ai#4541)) ([540a0fa](langchain-ai@540a0fa)) * The deprecated `files_update` attribute and constructor keyword are removed from `WriteResult` and `EditResult`. Custom backends must stop passing `files_update=`, and callers must stop reading `result.files_update`; state writes are emitted directly by `StateBackend`. ([langchain-ai#4541](langchain-ai#4541)) ([540a0fa](langchain-ai@540a0fa)) * Removed the deprecated `BackendProtocol` methods `ls_info`, `als_info`, `glob_info`, `aglob_info`, `grep_raw`, and `agrep_raw`. Use `ls` / `glob` / `grep` and their async counterparts. ([langchain-ai#4541](langchain-ai#4541)) ([540a0fa](langchain-ai@540a0fa)) * `SummarizationMiddleware(history_path_prefix=...)` was removed and now raises `TypeError`. Configure `CompositeBackend(artifacts_root=...)` instead. ([langchain-ai#4541](langchain-ai#4541)) ([540a0fa](langchain-ai@540a0fa)) * Agent-facing `ls` and `glob` tool output now renders empty results as `No files found` instead of `[]`; direct backend APIs continue to return structured empty `LsResult` and `GlobResult` values. Callers that parse tool output should update those checks. ([langchain-ai#3709](langchain-ai#3709)) ([efafd1e](langchain-ai@efafd1e)) * `read_file` no longer renders raw text with a fixed-width `cat -n`-style line-number gutter and tab separator. Line and continuation markers are dynamically aligned and separated from source content by two spaces, and the `LINE_NUMBER_WIDTH` constant is removed from `deepagents.backends.utils` and `deepagents.middleware.filesystem`. Callers that parse raw tool output should update those parsers. ([langchain-ai#4561](langchain-ai#4561)) ([cf057b4](langchain-ai@cf057b4)) ### Features * Custom middleware passed to `create_deep_agent(..., middleware=[...])` can replace a default middleware instance when `.name` matches, so defaults such as `SummarizationMiddleware` can be overridden without also excluding the built-in instance. ([langchain-ai#4251](langchain-ai#4251)) ([90c8472](langchain-ai@90c8472)) * `FilesystemMiddleware(tools=[...])` accepts a keyword-only allowlist of built-in filesystem tools, typed by the newly exported `FsToolName` literal (`"ls"`, `"read_file"`, `"write_file"`, `"edit_file"`, `"delete"`, `"glob"`, `"grep"`, `"execute"`); pass `"all"` or omit the argument to keep every tool. A list must include `"read_file"` or the constructor raises `ValueError`. Omitted built-in tools are non-executable, and custom user tools are unaffected. ([langchain-ai#4325](langchain-ai#4325)) ([langchain-ai#4698](langchain-ai#4698)) ([704a70d](langchain-ai@704a70d)) ([9709525](langchain-ai@9709525)) * Shorten LLM-facing descriptions for the `task` tool and filesystem tools (`read_file`, `grep`, `edit_file`, `glob`, `execute`). ([langchain-ai#5009](langchain-ai#5009)) ([761f5f0](langchain-ai@761f5f0)) * `GrepResult` and `GlobResult` now carry a `truncated` flag so supporting backends can return valid partial results when a match cap or backend deadline is reached; agent-facing tool output adds a note telling the model to narrow the search. `FilesystemBackend` returns partial `grep` and `glob` results on its backend timeout rather than erroring, while other backend or middleware timeouts may still return errors. Its `glob` also gains brace expansion such as `*.{py,md}` (already supported by the state and store backends). ([langchain-ai#4063](langchain-ai#4063)) ([ef591e7](langchain-ai@ef591e7)) * The agent-facing `grep` match cap is configurable: `FilesystemMiddleware(grep_max_count=...)` sets the default (`1000`; `None` disables it) and the model can override it per call through the tool's new `max_count` argument. `grep` / `agrep` on `BackendProtocol` and all built-in backends accept a keyword-only `max_count`. Local ripgrep output is streamed and terminated once the cap is reached. Direct `FilesystemBackend.grep()` callers can request surrounding lines with keyword-only `context_lines`. ([langchain-ai#4570](langchain-ai#4570)) ([langchain-ai#4706](langchain-ai#4706)) ([8e86f5e](langchain-ai@8e86f5e)) ([65230df](langchain-ai@65230df)) * Paginated built-in `read_file` responses report the returned source-line range and next `offset`; total and remaining line counts are included when the backend knows the file length. Resume offsets remain safe when sandbox or middleware limits shorten the visible page. ([langchain-ai#4540](langchain-ai#4540)) ([8321194](langchain-ai@8321194)) * Optional video frame extraction for `read_file`, enabled by the new `deepagents[video]` extra. Video files are sampled into JPEG frames, with `offset` and `limit` interpreted as seconds. Without the extra, existing generic video/file content-block behavior remains. ([langchain-ai#4094](langchain-ai#4094)) ([b927147](langchain-ai@b927147)) * `FilesystemMiddleware` can capture oversized `execute` tool output directly inside the sandbox artifact path on compatible, opted-in `BaseSandbox` implementations to reduce round trips; `LangSmithSandbox` opts in by default. ([langchain-ai#4230](langchain-ai#4230)) ([02f5bd7](langchain-ai@02f5bd7)) * Automatically enable Fireworks prompt-cache session affinity when a compatible `langchain-fireworks` installation is available. ([langchain-ai#4598](langchain-ai#4598)) ([5d878bf](langchain-ai@5d878bf)) * Add a built-in NVIDIA Nemotron 3 Ultra harness profile and NVIDIA NIM app-origin attribution. ([langchain-ai#4192](langchain-ai#4192)) ([langchain-ai#4455](langchain-ai#4455)) ([d5a60ec](langchain-ai@d5a60ec)) ([4cb4749](langchain-ai@4cb4749)) * `RubricMiddleware` now accepts any positive `max_iterations` cap instead of enforcing a hard upper bound. ([langchain-ai#4405](langchain-ai#4405)) ([d6692a7](langchain-ai@d6692a7)) ### Bug Fixes * Keep fields marked with `PrivateStateAttr`, including fields declared through `create_deep_agent(state_schema=...)`, out of subagent inputs and returned parent-state updates. ([langchain-ai#4587](langchain-ai#4587)) ([a4662c0](langchain-ai@a4662c0)) * Preserve `ContextT` through the `create_deep_agent(..., middleware=[...])` type annotation so type checkers accept context-aware middleware when a matching `context_schema` is passed. ([langchain-ai#4055](langchain-ai#4055)) ([7be76c7](langchain-ai@7be76c7)) * Accept YAML list values as well as comma-separated strings for skill `allowed-tools` frontmatter, and make skill truncation warnings actionable with field name, path, length, configured limit, and impact. ([langchain-ai#4140](langchain-ai#4140)) ([langchain-ai#4141](langchain-ai#4141)) ([d62534c](langchain-ai@d62534c)) ([2f5f5b8](langchain-ai@2f5f5b8)) * Align filesystem instructions with the tools that remain after allowlist and backend-capability filtering, so agents no longer reference hidden `grep`/`glob` tools or prohibit equivalent shell search when dedicated search tools are unavailable. ([langchain-ai#4920](langchain-ai#4920)) ([langchain-ai#4921](langchain-ai#4921)) ([d3650c7](langchain-ai@d3650c7)) ([b65cc00](langchain-ai@b65cc00)) * Propagate default-backend failures from `CompositeBackend.ls("/")` and `CompositeBackend.als("/")` instead of returning successful route-only listings. ([langchain-ai#4925](langchain-ai#4925)) ([4c3b166](langchain-ai@4c3b166)) * Correct `CompositeBackend.glob` / `CompositeBackend.aglob` routing so explicit default-backend paths such as `/tools` do not also return files from routed backends such as `/memories`. ([langchain-ai#4531](langchain-ai#4531)) ([cbdb0a7](langchain-ai@cbdb0a7)) * Propagate default- and routed-backend failures from root `CompositeBackend.glob(..., path=None)` / `aglob(..., path=None)` and `path="/"` searches instead of returning incomplete successful results. ([langchain-ai#4063](langchain-ai#4063)) ([ef591e7](langchain-ai@ef591e7)) * Constrain sandbox `glob` and slash-pattern `grep` searches to their declared search root by treating leading `/` as search-root-relative, rejecting `..` traversal segments, and filtering symlink-resolved matches outside the root. ([langchain-ai#4588](langchain-ai#4588)) ([c6c7213](langchain-ai@c6c7213)) * Unify `grep(..., glob=...)` include-glob semantics across filesystem and in-memory backends: basename patterns like `*.py` match at any depth, and slash-containing patterns like `src/**/*.py` match relative paths consistently. ([langchain-ai#3936](langchain-ai#3936)) ([feab6e0](langchain-ai@feab6e0)) * Improve agent-facing `grep` descriptions and no-match hints to steer regex-looking patterns toward literal searches, route slash-containing sandbox include-globs correctly, and shorten default search timeouts so bad patterns and huge trees return guidance faster. ([langchain-ai#4168](langchain-ai#4168)) ([b1dbf5e](langchain-ai@b1dbf5e)) * Align sandbox delete behavior with other backends by returning not-found errors for missing paths, and avoid over-blocking unrelated sibling deletes when deny rules use glob patterns. ([langchain-ai#4321](langchain-ai#4321)) ([d77496b](langchain-ai@d77496b)) * Improve rubric grader failure diagnostics with configured model, structured-output strategy, and integer HTTP status when available. ([langchain-ai#4938](langchain-ai#4938)) ([langchain-ai#4967](langchain-ai#4967)) ([f51d3a0](langchain-ai@f51d3a0)) ([bca70aa](langchain-ai@bca70aa)) * Emit `max_iterations_reached` as the terminal `RubricMiddleware` status when the iteration cap is exhausted, instead of a final `needs_revision` event that will not loop. ([langchain-ai#4406](langchain-ai#4406)) ([a51c8d2](langchain-ai@a51c8d2)) * Handle missing async subagent URLs consistently in `check_async_task` and `cancel_async_task`. ([langchain-ai#3967](langchain-ai#3967)) ([b0d92c0](langchain-ai@b0d92c0)) ### Performance Improvements * Run LangSmith sandbox commands over the async client. ([langchain-ai#5061](langchain-ai#5061)) ([0d08747](langchain-ai@0d08747)) --- _End release notes preview._ --- > [!NOTE] > A **New Contributors** section is appended to the GitHub release notes automatically at publish time (see [Release Pipeline](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md#release-pipeline), step 2). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Mason Daugherty <github@mdrxy.com>
> [!CAUTION] > Merging this PR will automatically publish to **PyPI** and create a **GitHub release**. For the full release process, see [`.github/RELEASING.md`](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md). --- _Release notes preview: keep this section in sync with the package `CHANGELOG.md`. Publish reads the merged CHANGELOG via `release.yml`, not this PR description — keep them aligned anyway so the PR stays an accurate historical record for reviewers and anyone returning later._ --- ## [0.7.0](deepagents==0.6.12...deepagents==0.7.0) (2026-07-29) See [the docs](https://docs.langchain.com/oss/python/releases/changelog#deepagents-v0-7-0) for curated release notes. ### ⚠ BREAKING CHANGES * `create_deep_agent` no longer includes `TodoListMiddleware` by default, the `write_todos` tool, `todos` state channel, and todo-planning prompt are now absent. Pass `middleware=[TodoListMiddleware()]` to restore them on the main agent; add it to each `SubAgent`'s middleware to restore them there. ([#4929](#4929)) ([9340518](9340518)) * Default agent prompts are now lean: the authored base prompt is empty, and tool-usage prose that duplicates tool schemas is trimmed. `BASE_AGENT_PROMPT` is deprecated (removal in `deepagents==0.9.0`) but remains importable and still returns the previous authored prompt verbatim; pass it as `create_deep_agent(system_prompt=BASE_AGENT_PROMPT)` to restore the old behavior. ([#4859](#4859)) ([#4979](#4979)) ([a8d1b32](a8d1b32)) ([d9f54fc](d9f54fc)) * The built-in tool-usage prompt constants `TASK_SYSTEM_PROMPT`, `ASYNC_TASK_SYSTEM_PROMPT`, `SUMMARIZATION_SYSTEM_PROMPT`, `FILESYSTEM_SYSTEM_PROMPT`, and `EXECUTION_SYSTEM_PROMPT` are removed, and the `system_prompt` default on `SubAgentMiddleware`, `AsyncSubAgentMiddleware`, `SummarizationToolMiddleware`, and `create_summarization_tool_middleware` is now `None`, which injects no prose. Pass your own string to restore prompt text. ([#4859](#4859)) ([a8d1b32](a8d1b32)) * `FilesystemBackend` and `LocalShellBackend` now default to `virtual_mode=True`. Filesystem paths are anchored under `root_dir`, `..` traversal is rejected, and paths resolving outside `root_dir` raise `ValueError`. Previously an unspecified `virtual_mode` emitted a deprecation warning and fell back to `False`, where absolute host paths were used as-is and `..` could escape `root_dir`. Pass `virtual_mode=False` explicitly to restore the old filesystem behavior. ([#4541](#4541)) ([540a0fa](540a0fa)) * Agents now see a destructive, recursive `delete` filesystem tool whenever the backend supports it, and filesystem permissions classify `delete` as a write operation — so an existing rule allowing writes to a path also authorizes recursively deleting that subtree unless a narrower deny or interrupt rule covers the target. Because recursive deletes affect descendants, deny and interrupt checks use bulk path overlap instead of exact-path matching. To keep the previous behavior, add a deny or interrupt rule, or omit `delete` from `FilesystemMiddleware(tools=...)`. Missing paths return a not-found error, `CompositeBackend` reports an unsupported-operation error when a routed sub-backend cannot delete, and the tool is hidden from the model entirely when the backend itself does not implement it. ([#3659](#3659)) ([#3691](#3691)) ([#3765](#3765)) ([#3851](#3851)) ([f2a21ec](f2a21ec)) * `write_file` can now create a file if it is missing and replaces it entirely if it already exists, instead of returning a file-exists error. The `write_file` tool description no longer requires reading the file first. There is no "create-only" compatibility mode. Workflows, prompts, tests, or guardrails that relied on the file-exists error to force `edit_file` usage or to protect existing content must omit `write_file`, add explicit permission or interrupt rules, or use `edit_file` where preserving existing content matters. ([#4109](#4109)) ([2506fcc](2506fcc)) * Removed deprecated backend compatibility shims. Callers must pass concrete `BackendProtocol` instances (not factories), configure `StoreBackend` with an explicit `namespace`, and use the current `ls` / `glob` / `grep` / `ReadResult` APIs. ([#4541](#4541)) ([540a0fa](540a0fa)) * The deprecated `files_update` attribute and constructor keyword are removed from `WriteResult` and `EditResult`. Custom backends must stop passing `files_update=`, and callers must stop reading `result.files_update`; state writes are emitted directly by `StateBackend`. ([#4541](#4541)) ([540a0fa](540a0fa)) * Removed the deprecated `BackendProtocol` methods `ls_info`, `als_info`, `glob_info`, `aglob_info`, `grep_raw`, and `agrep_raw`. Use `ls` / `glob` / `grep` and their async counterparts. ([#4541](#4541)) ([540a0fa](540a0fa)) * `SummarizationMiddleware(history_path_prefix=...)` was removed and now raises `TypeError`. Configure `CompositeBackend(artifacts_root=...)` instead. ([#4541](#4541)) ([540a0fa](540a0fa)) * Agent-facing `ls` and `glob` tool output now renders empty results as `No files found` instead of `[]`; direct backend APIs continue to return structured empty `LsResult` and `GlobResult` values. Callers that parse tool output should update those checks. ([#3709](#3709)) ([efafd1e](efafd1e)) * `read_file` no longer renders raw text with a fixed-width `cat -n`-style line-number gutter and tab separator. Line and continuation markers are dynamically aligned and separated from source content by two spaces, and the `LINE_NUMBER_WIDTH` constant is removed from `deepagents.backends.utils` and `deepagents.middleware.filesystem`. Callers that parse raw tool output should update those parsers. ([#4561](#4561)) ([cf057b4](cf057b4)) ### Features * Custom middleware passed to `create_deep_agent(..., middleware=[...])` can replace a default middleware instance when `.name` matches, so defaults such as `SummarizationMiddleware` can be overridden without also excluding the built-in instance. ([#4251](#4251)) ([90c8472](90c8472)) * `FilesystemMiddleware(tools=[...])` accepts a keyword-only allowlist of built-in filesystem tools, typed by the newly exported `FsToolName` literal (`"ls"`, `"read_file"`, `"write_file"`, `"edit_file"`, `"delete"`, `"glob"`, `"grep"`, `"execute"`); pass `"all"` or omit the argument to keep every tool. A list must include `"read_file"` or the constructor raises `ValueError`. Omitted built-in tools are non-executable, and custom user tools are unaffected. ([#4325](#4325)) ([#4698](#4698)) ([704a70d](704a70d)) ([9709525](9709525)) * Shorten LLM-facing descriptions for the `task` tool and filesystem tools (`read_file`, `grep`, `edit_file`, `glob`, `execute`). ([#5009](#5009)) ([761f5f0](761f5f0)) * `GrepResult` and `GlobResult` now carry a `truncated` flag so supporting backends can return valid partial results when a match cap or backend deadline is reached; agent-facing tool output adds a note telling the model to narrow the search. `FilesystemBackend` returns partial `grep` and `glob` results on its backend timeout rather than erroring, while other backend or middleware timeouts may still return errors. Its `glob` also gains brace expansion such as `*.{py,md}` (already supported by the state and store backends). ([#4063](#4063)) ([ef591e7](ef591e7)) * The agent-facing `grep` match cap is configurable: `FilesystemMiddleware(grep_max_count=...)` sets the default (`1000`; `None` disables it) and the model can override it per call through the tool's new `max_count` argument. `grep` / `agrep` on `BackendProtocol` and all built-in backends accept a keyword-only `max_count`. Local ripgrep output is streamed and terminated once the cap is reached. Direct `FilesystemBackend.grep()` callers can request surrounding lines with keyword-only `context_lines`. ([#4570](#4570)) ([#4706](#4706)) ([8e86f5e](8e86f5e)) ([65230df](65230df)) * Paginated built-in `read_file` responses report the returned source-line range and next `offset`; total and remaining line counts are included when the backend knows the file length. Resume offsets remain safe when sandbox or middleware limits shorten the visible page. ([#4540](#4540)) ([8321194](8321194)) * Optional video frame extraction for `read_file`, enabled by the new `deepagents[video]` extra. Video files are sampled into JPEG frames, with `offset` and `limit` interpreted as seconds. Without the extra, existing generic video/file content-block behavior remains. ([#4094](#4094)) ([b927147](b927147)) * `FilesystemMiddleware` can capture oversized `execute` tool output directly inside the sandbox artifact path on compatible, opted-in `BaseSandbox` implementations to reduce round trips; `LangSmithSandbox` opts in by default. ([#4230](#4230)) ([02f5bd7](02f5bd7)) * Automatically enable Fireworks prompt-cache session affinity when a compatible `langchain-fireworks` installation is available. ([#4598](#4598)) ([5d878bf](5d878bf)) * Add a built-in NVIDIA Nemotron 3 Ultra harness profile and NVIDIA NIM app-origin attribution. ([#4192](#4192)) ([#4455](#4455)) ([d5a60ec](d5a60ec)) ([4cb4749](4cb4749)) * `RubricMiddleware` now accepts any positive `max_iterations` cap instead of enforcing a hard upper bound. ([#4405](#4405)) ([d6692a7](d6692a7)) ### Bug Fixes * Keep fields marked with `PrivateStateAttr`, including fields declared through `create_deep_agent(state_schema=...)`, out of subagent inputs and returned parent-state updates. ([#4587](#4587)) ([a4662c0](a4662c0)) * Preserve `ContextT` through the `create_deep_agent(..., middleware=[...])` type annotation so type checkers accept context-aware middleware when a matching `context_schema` is passed. ([#4055](#4055)) ([7be76c7](7be76c7)) * Accept YAML list values as well as comma-separated strings for skill `allowed-tools` frontmatter, and make skill truncation warnings actionable with field name, path, length, configured limit, and impact. ([#4140](#4140)) ([#4141](#4141)) ([d62534c](d62534c)) ([2f5f5b8](2f5f5b8)) * Align filesystem instructions with the tools that remain after allowlist and backend-capability filtering, so agents no longer reference hidden `grep`/`glob` tools or prohibit equivalent shell search when dedicated search tools are unavailable. ([#4920](#4920)) ([#4921](#4921)) ([d3650c7](d3650c7)) ([b65cc00](b65cc00)) * Propagate default-backend failures from `CompositeBackend.ls("/")` and `CompositeBackend.als("/")` instead of returning successful route-only listings. ([#4925](#4925)) ([4c3b166](4c3b166)) * Correct `CompositeBackend.glob` / `CompositeBackend.aglob` routing so explicit default-backend paths such as `/tools` do not also return files from routed backends such as `/memories`. ([#4531](#4531)) ([cbdb0a7](cbdb0a7)) * Propagate default- and routed-backend failures from root `CompositeBackend.glob(..., path=None)` / `aglob(..., path=None)` and `path="/"` searches instead of returning incomplete successful results. ([#4063](#4063)) ([ef591e7](ef591e7)) * Constrain sandbox `glob` and slash-pattern `grep` searches to their declared search root by treating leading `/` as search-root-relative, rejecting `..` traversal segments, and filtering symlink-resolved matches outside the root. ([#4588](#4588)) ([c6c7213](c6c7213)) * Unify `grep(..., glob=...)` include-glob semantics across filesystem and in-memory backends: basename patterns like `*.py` match at any depth, and slash-containing patterns like `src/**/*.py` match relative paths consistently. ([#3936](#3936)) ([feab6e0](feab6e0)) * Improve agent-facing `grep` descriptions and no-match hints to steer regex-looking patterns toward literal searches, route slash-containing sandbox include-globs correctly, and shorten default search timeouts so bad patterns and huge trees return guidance faster. ([#4168](#4168)) ([b1dbf5e](b1dbf5e)) * Align sandbox delete behavior with other backends by returning not-found errors for missing paths, and avoid over-blocking unrelated sibling deletes when deny rules use glob patterns. ([#4321](#4321)) ([d77496b](d77496b)) * Improve rubric grader failure diagnostics with configured model, structured-output strategy, and integer HTTP status when available. ([#4938](#4938)) ([#4967](#4967)) ([f51d3a0](f51d3a0)) ([bca70aa](bca70aa)) * Emit `max_iterations_reached` as the terminal `RubricMiddleware` status when the iteration cap is exhausted, instead of a final `needs_revision` event that will not loop. ([#4406](#4406)) ([a51c8d2](a51c8d2)) * Handle missing async subagent URLs consistently in `check_async_task` and `cancel_async_task`. ([#3967](#3967)) ([b0d92c0](b0d92c0)) ### Performance Improvements * Run LangSmith sandbox commands over the async client. ([#5061](#5061)) ([0d08747](0d08747)) --- _End release notes preview._ --- > [!NOTE] > A **New Contributors** section is appended to the GitHub release notes automatically at publish time (see [Release Pipeline](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md#release-pipeline), step 2). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Mason Daugherty <github@mdrxy.com>
The filesystem delete tool only removed single files and rejected directories, so clearing a directory (e.g. a subagent's working dir) meant one tool call per file and left the empty directories behind. This makes delete remove directories recursively, renames the tool from
delete_filetodelete, and makes the permission layer treat a recursive delete as a single all-or-nothing operation over the whole subtree.What changed:
FilesystemBackend.deletenow removes directories recursively (shutil.rmtree). Symlinks are removed as links, never followed into their target.delete_filetodelete(the arg is stillfile_path).deletemaps to thewriteoperation. For a directory target, the tool expands the subtree and checkswriteon the target and every descendant; if any path is denied, the entire delete is refused and nothing is removed (all-or-nothing).deleteis now registered forinterrupt-mode permissions (bulk scope), so an interrupt rule on any path inside the target fires before the delete runs. It was previously absent, so interrupt rules never applied to deletes.Why all-or-nothing: a recursive delete of a directory is one intent. If part of it is protected, refusing cleanly is safer and more predictable than leaving a half-deleted tree, and the agent can still delete the allowed paths explicitly.