Skip to content

Add fileaccess harness context provider with shared-folder file tools rooted at a directory - #643

Open
PratikDhanave (PratikDhanave) wants to merge 2 commits into
microsoft:mainfrom
PratikDhanaveFork:fileaccess-harness-provider
Open

Add fileaccess harness context provider with shared-folder file tools rooted at a directory#643
PratikDhanave (PratikDhanave) wants to merge 2 commits into
microsoft:mainfrom
PratikDhanaveFork:fileaccess-harness-provider

Conversation

@PratikDhanave

Copy link
Copy Markdown
Contributor

What

Adds a new self-contained agent/harness/fileaccess package, wired like the sibling agent/harness/todo provider. New(*Options) returns a Provider backed by agent.NewContextProvider, and its Provide hook injects file tools plus instructions on each invocation.

The provider exposes the same six tools as the .NET FileAccessProvider:

  • file_access_read_file
  • file_access_save_file
  • file_access_list_files
  • file_access_list_subdirectories
  • file_access_search_files
  • file_access_delete_file

All operations go through a local-filesystem store rooted at a caller-granted Options.RootDir (not session state). Options.ReadOnly omits the save/delete tools, matching the .NET read-only shipping mode.

Why

The Go harness tree (agent/harness/) had agentmode, loop, todo, toolapproval, and toolautocall, but no file-access counterpart, while the .NET SDK ships FileAccessProvider with exactly this tool set (file_access_read_file / save_file / list_files / list_subdirectories / search_files / delete_file) and a read-only mode. This closes that cross-SDK parity gap so Go agents can be granted a scoped shared folder.

Safety

Every path is interpreted relative to the root. The store resolves paths with filepath.Clean(filepath.Join(root, rel)) and rejects anything that is absolute or escapes the root via a prefix check, so ../outside.txt and absolute paths are refused before touching the filesystem.

Tests

fileaccess_test.go is black-box (package fileaccess_test) and reuses the same harness style as todo_test.go (agenttest.CreateSession, driving tools through the exported Invoking API). It covers: default tool set + instructions present; ReadOnly omitting save/delete; save->read round trip; list_files direct-children-only vs list_subdirectories; search_files regex across nested files; delete; and path-escape rejection (../outside.txt, absolute path, escaping write not creating a file).

go build ./..., go vet ./agent/harness/fileaccess/..., and go test ./agent/harness/fileaccess/... all pass.

Open design questions

  • Scope: The store is a minimal local-filesystem implementation held inside the package. Should it instead be an exported interface so callers can back it with other stores (blob, in-memory), mirroring any abstraction on the .NET side?
  • API shape: Tool argument/return shapes (relative-path strings, search_files returning slash-separated relative paths matched against file contents) are chosen for parity; happy to align field names/semantics exactly with the .NET tool schemas if they differ.
  • Follow-ups: The .NET provider ships read-only mode as an auto-approval rule via the tool-approval harness. This PR keeps the package self-contained (ReadOnly simply omits the mutating tools); wiring an explicit auto-approval rule through toolapproval could be a follow-up.

@github-actions

This comment has been minimized.

@github-actions github-actions Bot added the public-api-change Pull Request changes public APIs label Jul 24, 2026
Introduce a self-contained agent/harness/fileaccess package that mirrors the
.NET FileAccessProvider. It registers a context provider that injects file
tools scoped to a caller-granted root directory: file_access_read_file,
file_access_save_file, file_access_list_files, file_access_list_subdirectories,
file_access_search_files, and file_access_delete_file.

The root comes from Options.RootDir (not session state). A local-filesystem
store constrains every operation to the root, rejecting absolute paths and
".." traversal via filepath.Clean plus a prefix check. Options.ReadOnly omits
the save and delete tools to match the .NET read-only shipping mode.
@github-actions

This comment has been minimized.

@PratikDhanave
PratikDhanave (PratikDhanave) marked this pull request as ready for review August 4, 2026 06:06
@PratikDhanave
PratikDhanave (PratikDhanave) requested a review from a team as a code owner August 4, 2026 06:06
Copilot AI lite review requested due to automatic review settings August 4, 2026 06:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Go harness context provider (agent/harness/fileaccess) that injects shared-folder file tools (read/save/list/search/delete) into agent invocations, intended to mirror the .NET FileAccessProvider and support a read-only mode.

Changes:

  • Introduces fileaccess.Provider with tool injection + default/read-only instructions and a local filesystem-backed store rooted at Options.RootDir.
  • Implements six file tools (file_access_*) with path resolution intended to constrain access to the configured root directory, plus read-only mode by omitting mutating tools.
  • Adds black-box tests validating tool exposure, read-only behavior, round-trip save/read, list/search semantics, delete, and basic ../absolute-path escape rejection.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
agent/harness/fileaccess/fileaccess.go New file-access provider and local filesystem store for shared-folder tool operations.
agent/harness/fileaccess/fileaccess_test.go New black-box tests covering tool presence/behavior, basic path escape rejection, and core operations.
Suppressed comments (1)

agent/harness/fileaccess/fileaccess.go:283

  • Path containment checks do not account for symlinks inside the root. For example, if the shared folder contains a symlink directory like "link" -> "/tmp", calling save_file with path "link/outside.txt" will pass resolve() (it stays under root textually) but os.MkdirAll/os.WriteFile will follow the symlink and write outside the root. The same issue applies to read/delete/search for symlink files.
func (s *store) SaveFile(rel, content string) error {
	full, err := s.resolve(rel)
	if err != nil {
		return err
	}

Comment on lines +10 to +13
// All operations are constrained to the configured root directory. Paths are
// resolved relative to the root and any attempt to escape it (via "..", an
// absolute path, or symlink-style traversal in the supplied name) is rejected.
//
Comment on lines +258 to +262
full := filepath.Clean(filepath.Join(s.root, rel))
if full != s.root && !strings.HasPrefix(full, s.root+string(os.PathSeparator)) {
return "", fmt.Errorf("path %q escapes the shared folder root", rel)
}
return full, nil
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Cross-SDK Parity Review — PR #643

This PR adds agent/harness/fileaccess, a Go port of the upstream FileAccessProvider. The feature is well-motivated and closes a real gap, but there are several parity divergences between the Go implementation and the upstream .NET (dotnet/src/Microsoft.Agents.AI/Harness/FileAccess/) and Python (python/packages/core/agent_framework/_harness/_file_access.py) equivalents worth addressing before merging.


🔴 Tool-name set is out of sync with .NET and Python

Both upstream SDKs expose seven tools, not six:

Capability .NET tool name Python tool name Go tool name
Write/save file_access_write file_access_write file_access_save_file
Read file_access_read file_access_read file_access_read_file
Delete file_access_delete file_access_delete file_access_delete_file
List (combined) file_access_ls file_access_ls split into two tools ✦
List files only file_access_list_files
List subdirs only file_access_list_subdirectories
Grep/search file_access_grep file_access_grep file_access_search_files
Replace substring file_access_replace file_access_replace missing
Replace lines file_access_replace_lines file_access_replace_lines missing

✦ = name differs from upstream convention

Key divergences:

  1. file_access_save_file vs file_access_write — Both .NET and Python use file_access_write. The Go name diverges and will confuse multi-SDK consumers.
  2. file_access_read_file vs file_access_read — Upstream is file_access_read.
  3. file_access_delete_file vs file_access_delete — Upstream is file_access_delete.
  4. file_access_ls is split into two tools — Both .NET and Python expose a single file_access_ls that returns files and subdirectories together. The Go implementation splits this into file_access_list_files and file_access_list_subdirectories, changing the agent-observable interface.
  5. file_access_search_files vs file_access_grep — Upstream uses file_access_grep in both SDKs.
  6. file_access_replace and file_access_replace_lines are absent — These two mutation tools are present in both .NET and Python. Their omission means Go agents will rewrite whole files for small edits while .NET and Python agents use targeted replace tools.

Upstream references:

  • .NET: dotnet/src/Microsoft.Agents.AI/Harness/FileAccess/FileAccessProvider.csWriteToolName, ReadFileToolName, DeleteFileToolName, LsToolName, GrepToolName, ReplaceToolName, ReplaceLinesToolName
  • Python: python/packages/core/agent_framework/_harness/_file_access.py — same seven tool names

🟡 ReadOnly vs DisableWriteTools

The option flag name differs: Go uses Options.ReadOnly while .NET uses FileAccessProviderOptions.DisableWriteTools. The semantics are equivalent; flagged here for a deliberate naming decision.


🟡 Approval / ToolApproval integration absent

In .NET, FileAccessProvider wraps every tool in ApprovalRequiredAIFunction by default and ships ReadOnlyToolsAutoApprovalRule and AllToolsAutoApprovalRule. Python mirrors this. The PR's description acknowledges this as a follow-up, so it is flagged here as a tracked parity gap.


🟡 file_access_write refuses to overwrite by default (Python)

Python's file_access_write rejects overwrites unless overwrite=True is set. The Go file_access_save_file silently overwrites. If the tool names are aligned, overwrite behavior should be reconciled too.


🟢 What is aligned

  • Path-escape safety (reject ../ and absolute paths) matches both upstream SDKs.
  • Regex-based recursive content search matches upstream intent.
  • Read-only mode concept (omitting write/delete tools) matches upstream intent.
  • Instructions-injection via ContextProvider is consistent with the rest of the Go harness tree.

No example added

Python ships a worked sample at python/samples/02-agents/context_providers/file_access_data_processing/. Adding a corresponding Go example in examples/ would improve sample parity (lower priority than tool-name alignment).

Generated by Go API Consistency Review Agent · sonnet46 · 32.8 AIC · ⌖ 5.38 AIC · ⊞ 5.7K ·

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

public-api-change Pull Request changes public APIs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants