Python: [BREAKING] Align FileAccess tools with .NET — directory discovery and recursive search#6476
Python: [BREAKING] Align FileAccess tools with .NET — directory discovery and recursive search#6476westey-m wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Aligns the Python file-access harness/tooling with the .NET surface by adding directory discovery and making store-level search optionally recursive, while updating the provider to expose a dedicated subdirectory listing tool and a root-recursive search tool.
Changes:
- Added
AgentFileStore.list_directories()and a keyword-onlyrecursiveflag tosearch_files()(implemented for both in-memory and filesystem stores). - Updated
FileAccessProviderto addfile_access_list_subdirectoriesand to makefile_access_search_filessearch recursively from the store root (scoped via glob). - Updated tests and sample/docs to cover recursive search, directory discovery, and filesystem symlink behavior.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| python/samples/02-agents/harness/console/observers/planning_models.py | Expands structured-output schema guidance for clarification choices. |
| python/samples/02-agents/context_providers/file_access_data_processing/data_processing.py | Updates sample instructions/comments to mention subdirectory exploration and the new tool count. |
| python/packages/core/tests/core/test_harness_file_access.py | Adds/updates unit tests for recursive search, directory listing, symlink handling, and provider tool surface. |
| python/packages/core/AGENTS.md | Updates harness documentation to reflect new store/provider capabilities and semantics. |
| python/packages/core/agent_framework/_harness/_file_access.py | Implements list_directories, recursive search behavior, and adds the new file_access_list_subdirectories tool + updated search tool semantics. |
Python Test Coverage Report •
Python Unit Test Overview
|
||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Automated Code Review
Reviewers: 5 | Confidence: 91%
✓ Correctness
The core file access changes (recursive search,
list_directories, symlink skipping) are logically correct and well-tested. The only correctness issue is inplanning_models.pywhere implicit Python string concatenation produces sentences joined without spaces, creating malformed description text sent to the LLM.
✓ Security Reliability
The file-access changes are well-implemented from a security/reliability standpoint: path traversal is rejected by
_normalize_relative_path, symlinks are correctly skipped in both_enumerate_search_filesand_list_directories_sync, the recursive search is bounded by_run_search_with_timeout, and errors are caught at the tool boundary. The one issue found is inplanning_models.pywhere adjacent Python string literals are missing separators, producing garbled prompt text.
✓ Test Coverage
The test coverage for this PR is thorough. New tests cover recursive search (both stores), list_directories (both stores), path traversal rejection, symlinked directory exclusion, provider-level tool registration, and round-trip integration. The tests validate meaningful assertions (exact file sets, security boundaries, correct relative paths). One minor gap: symlinked files are skipped by the implementation but only symlinked directories are explicitly tested; however, the same is_symlink() check handles both. No blocking issues found.
✓ Failure Modes
The core file-access changes (recursive search, list_directories) are well-implemented with consistent error handling and proper security boundaries. However, the
planning_models.pychange introduces garbled description text due to missing whitespace between implicitly concatenated strings. The recursive_enumerate_search_fileshelper inherits the existing lack of per-directory PermissionError handling, which means a single unreadable subdirectory now aborts an entire recursive search (a wider failure surface than before, though the provider's OSError catch prevents a hard crash).
✓ Design Approach
I found one design-parity issue in the new store surface:
InMemoryAgentFileStorenow advertises directory discovery vialist_directories, but it still treatscreate_directoryas a no-op, so empty directories are never discoverable there even though the filesystem implementation does surface them. I did not find a stronger blocking issue in the provider-level recursive search change itself.
Suggestions
- Make
InMemoryAgentFileStorepersist explicit directories socreate_directory()andlist_directories()model the same contract asFileSystemAgentFileStore.
Automated review by westey-m's agents
Addresses PR review: separate concatenated string literals with proper spacing/newlines, wrap lines under the 120-char Ruff limit, and fix "doesn't" -> "don't". Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Motivation and Context
This brings the Python file-access harness in line with the .NET changes in #6474. Two gaps existed in the Python harness:
file_access_list_filesonly returns direct-child files, so an agent could not discover that subdirectories exist.file_access_search_filesonly scanned the direct children of a single directory, with no way to search an entire subtree.#6398
Description
Mirrors the .NET surface (with one intentional, documented divergence on glob semantics).
Store layer (
AgentFileStore+ both implementations)list_directories(directory="")— direct child subdirectories only.recursive: bool = Falsetosearch_files; the glob and each result'sfile_nameare relative todirectory, andrecursive=Truewalks all descendants.InMemoryAgentFileStore: recursive relative-path glob/file_name;list_directoriescollects the distinct first path segment of nested keys (case-preserving, case-insensitive de-dup).FileSystemAgentFileStore: recursive walk that prunes symlinked directories and skips symlinked files;list_directoriesexcludes symlinked dirs and returns[]for a missing directory.Provider (
FileAccessProvider)file_access_search_files: removed thedirectoryparameter; now searches recursively from the store root and returns store-root-relativefile_namepaths. Scoping is via the glob.file_access_list_subdirectoriestool.DEFAULT_FILE_ACCESS_INSTRUCTIONSto mention subdirectory exploration and recursive search.Intentional divergence from .NET: Python keeps
fnmatch, where*already crosses/(e.g.*.mdmatches markdown at any depth,reports/*scopes to a subtree). This differs from .NET's**matcher and is documented to the model via the tool description.Tests & docs
list_directories(in-memory + filesystem), filesystem symlinked-directory skipping, and path-traversal rejection forlist_directorieson both stores.python/packages/core/AGENTS.mdand thefile_access_data_processingsample.The Python
_harness/_memory.pyprovider does not call the store search/list APIs, andsearch_filesdefaults torecursive=False, so existing behavior is preserved.Contribution Checklist