Skip to content

Commit a2f56f7

Browse files
authored
Python: [BREAKING] Improve FileAccess/FileMemory harness providers (surgical edits, read-only tier, consistent naming) (microsoft#6801)
* Python: Add additional file access and memory provider functionality and renames for simplification and consistency * Address PR comments and build failures. * Address PR comments * Address PR comments
1 parent e7c7f74 commit a2f56f7

11 files changed

Lines changed: 1054 additions & 593 deletions

File tree

python/packages/core/AGENTS.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,18 @@ agent_framework/
9595

9696
### File Access Harness (`_harness/_file_access.py`)
9797

98-
- **`AgentFileStore`** - Abstract async store backing the file-access harness. Implementations expose `write_file`, `read_file`, `delete_file`, `list_files`, `list_directories`, `file_exists`, `search_files`, and `create_directory` over forward-slash relative paths. `list_files`/`list_directories` return only direct children; `search_files` accepts a keyword-only `recursive` flag (default `False`) and, when `recursive=True`, walks all descendants and returns `file_name` values relative to the search directory.
98+
- **`AgentFileStore`** - Abstract async store backing the file-access harness. Implementations expose `write`, `read`, `delete`, `list_children`, `file_exists`, `search`, and `create_directory` over forward-slash relative paths. `list_children` returns the direct children (files and subdirectories, subdirectories first) as `FileStoreEntry` instances; `search` accepts a keyword-only `recursive` flag (default `False`) and, when `recursive=True`, walks all descendants and returns `file_name` values relative to the search directory.
9999
- **`InMemoryAgentFileStore`** - Dict-backed store suitable for tests and lightweight scenarios.
100100
- **`FileSystemAgentFileStore`** - Disk-backed store rooted under a configurable directory. Enforces relative-path normalization, root containment, and rejects symlink/reparse-point segments to prevent escape.
101-
- **`FileSearchResult`** / **`FileSearchMatch`** - `SerializationMixin` DTOs returned by `search_files`, carrying the matching file name, a context snippet, and the matching lines with 1-based line numbers.
102-
- **`FileAccessProvider`** - `ContextProvider` that adds shared file-access tools (`file_access_save_file`, `file_access_read_file`, `file_access_delete_file`, `file_access_list_files`, `file_access_list_subdirectories`, `file_access_search_files`) plus default usage instructions to each invocation. `file_access_list_files`/`file_access_list_subdirectories` enumerate direct children (files / subdirectories) so the agent can walk the tree level by level; `file_access_search_files` searches recursively from the store root and returns store-root-relative `file_name` paths, scoped via an `fnmatch` glob (where `*` crosses `/`, e.g. `*.md`, `reports/*`). All six tools are registered with `approval_mode="always_require"`, so every file operation needs host approval. To run unattended, pass one of the static auto-approval rules to `ToolApprovalMiddleware` (via `auto_approval_rules`): `FileAccessProvider.read_only_tools_auto_approval_rule` approves only the read-only tools (read, list files, list subdirectories, search), while `FileAccessProvider.all_tools_auto_approval_rule` approves every file-access tool including save and delete. Both rules reject any call carrying a `server_label` so they stay scoped to this provider's local tools and never auto-approve a same-named hosted tool. The tool names are also exposed as class constants (`SAVE_FILE_TOOL_NAME`, `READ_FILE_TOOL_NAME`, `DELETE_FILE_TOOL_NAME`, `LIST_FILES_TOOL_NAME`, `LIST_SUBDIRECTORIES_TOOL_NAME`, `SEARCH_FILES_TOOL_NAME`). Unlike `MemoryContextProvider`, the store is intentionally shared across sessions and agents.
101+
- **`FileSearchResult`** / **`FileSearchMatch`** - `SerializationMixin` DTOs returned by `search`, carrying the matching file name, a context snippet, and the matching lines with 1-based line numbers.
102+
- **`FileStoreEntry`** - `SerializationMixin` DTO returned by `list_children`, carrying an entry `name` and `type` (`"file"` or `"directory"`).
103+
- **`FileAccessProvider`** - `ContextProvider` that adds shared file-access tools (`file_access_write`, `file_access_read`, `file_access_delete`, `file_access_ls`, `file_access_grep`, `file_access_replace`, `file_access_replace_lines`) plus default usage instructions to each invocation. `file_access_ls` enumerates direct children (both files and subdirectories) as `{name, type}` entries with an optional `glob_pattern`, so the agent can walk the tree level by level; `file_access_grep` searches recursively from an optional base `directory` and returns relative `file_name` paths, scoped via an `fnmatch` `glob_pattern` (where `*` crosses `/`, e.g. `*.md`, `reports/*`). `file_access_replace` substitutes `old_string` with `new_string` (failing if not found, or if multiple matches and `replace_all` is false); `file_access_replace_lines` replaces whole 1-based lines. All tools are registered with `approval_mode="always_require"`, so every file operation needs host approval. Pass `disable_write_tools=True` to advertise only the read-only tools. To run unattended, pass one of the static auto-approval rules to `ToolApprovalMiddleware` (via `auto_approval_rules`): `FileAccessProvider.read_only_tools_auto_approval_rule` approves only the read-only tools (read, ls, grep), while `FileAccessProvider.all_tools_auto_approval_rule` approves every file-access tool including the write tools. Both rules reject any call carrying a `server_label` so they stay scoped to this provider's local tools and never auto-approve a same-named hosted tool. The tool names are also exposed as class constants (`WRITE_TOOL_NAME`, `READ_TOOL_NAME`, `DELETE_TOOL_NAME`, `LS_TOOL_NAME`, `GREP_TOOL_NAME`, `REPLACE_TOOL_NAME`, `REPLACE_LINES_TOOL_NAME`). Unlike `MemoryContextProvider`, the store is intentionally shared across sessions and agents.
103104

104105
### File Memory Harness (`_harness/_file_memory.py`)
105106

106-
- **`FileMemoryProvider`** - `ContextProvider` that gives an agent a session-scoped, file-based memory backed by the same `AgentFileStore` abstraction. Adds five tools (`file_memory_save_file`, `file_memory_read_file`, `file_memory_delete_file`, `file_memory_list_files`, `file_memory_search_files`) plus default usage instructions. Port of the .NET `FileMemoryProvider`.
107+
- **`FileMemoryProvider`** - `ContextProvider` that gives an agent a session-scoped, file-based memory backed by the same `AgentFileStore` abstraction. Adds tools (`file_memory_write`, `file_memory_read`, `file_memory_delete`, `file_memory_ls`, `file_memory_grep`, `file_memory_replace`, `file_memory_replace_lines`) plus default usage instructions. Port of the .NET `FileMemoryProvider`.
107108
- **Scoping** - Memories are isolated per session by default: each session writes under a working folder derived from `context.session_id`. Pass an explicit `scope` (e.g. a user id) to group memories across sessions, mirroring `FoundryMemoryProvider`'s `scope` arg.
108-
- **Descriptions & index** - `file_memory_save_file` accepts an optional `description`, stored in a companion `<stem>_description.md` sidecar. After each save/delete the provider rebuilds a capped (50-entry) `memories.md` index, and `before_run` injects that index as a `user` context message so the model knows what memories exist. Sidecars and the index are internal files hidden from `file_memory_list_files`/`file_memory_search_files` and rejected as save targets.
109+
- **Descriptions & index** - `file_memory_write` accepts an optional `description`, stored in a companion `<stem>_description.md` sidecar. After each write/delete the provider rebuilds a capped (50-entry) `memories.md` index, and `before_run` injects that index as a `user` context message so the model knows what memories exist. Sidecars and the index are internal files hidden from `file_memory_ls`/`file_memory_grep` and rejected as write targets.
109110
- **`DEFAULT_FILE_MEMORY_SOURCE_ID`** / **`DEFAULT_FILE_MEMORY_INSTRUCTIONS`** - Public defaults for the provider's source id and instruction banner.
110111
- **Harness wiring** - `create_harness_agent` includes both `FileMemoryProvider` and `FileAccessProvider` by default. Disable via `disable_file_memory` / `disable_file_access`; override the backing store via `file_memory_store` / `file_access_store`. When no store is supplied, defaults are `FileSystemAgentFileStore` rooted at `{cwd}/agent-file-memory` (memory) and `{cwd}/working` (access), mirroring the .NET `HarnessAgent`.
111112

python/packages/core/agent_framework/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
FileAccessProvider,
100100
FileSearchMatch,
101101
FileSearchResult,
102+
FileStoreEntry,
102103
FileSystemAgentFileStore,
103104
InMemoryAgentFileStore,
104105
)
@@ -447,6 +448,7 @@
447448
"FileSkill",
448449
"FileSkillScript",
449450
"FileSkillsSource",
451+
"FileStoreEntry",
450452
"FileSystemAgentFileStore",
451453
"FilteringSkillsSource",
452454
"FinalT",

python/packages/core/agent_framework/_harness/_agent.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ def _assemble_context_providers(
134134
file_memory_store: AgentFileStore | None,
135135
disable_file_access: bool,
136136
file_access_store: AgentFileStore | None,
137+
file_access_disable_write_tools: bool,
137138
skills_provider: SkillsProvider | None,
138139
skills_paths: str | Path | Sequence[str | Path] | None,
139140
background_agents: Sequence[SupportsAgentRun] | None,
@@ -167,7 +168,7 @@ def _assemble_context_providers(
167168
# Shared file access (on by default). Default store is rooted at ``{cwd}/working``.
168169
if not disable_file_access:
169170
access_store = file_access_store or FileSystemAgentFileStore(Path.cwd() / "working")
170-
providers.append(FileAccessProvider(access_store))
171+
providers.append(FileAccessProvider(access_store, disable_write_tools=file_access_disable_write_tools))
171172

172173
# Skills are opt-in: only added when skills_provider or skills_paths is provided.
173174
if skills_provider:
@@ -262,6 +263,7 @@ def create_harness_agent(
262263
file_memory_store: AgentFileStore | None = None,
263264
disable_file_access: bool = False,
264265
file_access_store: AgentFileStore | None = None,
266+
file_access_disable_write_tools: bool = False,
265267
skills_provider: SkillsProvider | None = None,
266268
skills_paths: str | Path | Sequence[str | Path] | None = None,
267269
background_agents: Sequence[SupportsAgentRun] | None = None,
@@ -374,6 +376,10 @@ def create_harness_agent(
374376
file_access_store: Custom AgentFileStore backing the FileAccessProvider. When None
375377
(and disable_file_access is False), a FileSystemAgentFileStore rooted at
376378
``{cwd}/working`` is created. Ignored when disable_file_access is True.
379+
file_access_disable_write_tools: When True, the FileAccessProvider advertises only its
380+
read-only tools (read, ls, grep); the write tools (write, delete, replace,
381+
replace_lines) are hidden. When False (default), all tools are advertised. Ignored
382+
when disable_file_access is True.
377383
skills_provider: Custom SkillsProvider instance for code-defined skills.
378384
Can be combined with ``skills_paths`` to aggregate file and code-based skills.
379385
skills_paths: Paths for file-based skill discovery (looks for SKILL.md files).
@@ -481,6 +487,7 @@ def create_harness_agent(
481487
file_memory_store=file_memory_store,
482488
disable_file_access=disable_file_access,
483489
file_access_store=file_access_store,
490+
file_access_disable_write_tools=file_access_disable_write_tools,
484491
skills_provider=skills_provider,
485492
skills_paths=skills_paths,
486493
background_agents=background_agents,

0 commit comments

Comments
 (0)