Skip to content

Add filememory harness context provider with session-scoped file_memory_* tools - #644

Open
PratikDhanave (PratikDhanave) wants to merge 3 commits into
microsoft:mainfrom
PratikDhanaveFork:add-filememory-harness
Open

Add filememory harness context provider with session-scoped file_memory_* tools#644
PratikDhanave (PratikDhanave) wants to merge 3 commits into
microsoft:mainfrom
PratikDhanaveFork:add-filememory-harness

Conversation

@PratikDhanave

Copy link
Copy Markdown
Contributor

What

Adds a new agent/harness/filememory package: a context provider that gives agents a small session-scoped virtual file store for persisting notes, scratch data, and intermediate results across turns of a long-running task.

The provider exposes five tools, constructed with functool.MustNew and returned from Provide as agent.WithTool options:

  • file_memory_write — create or overwrite a file
  • file_memory_read — read a file's content (empty string when absent)
  • file_memory_delete — remove a file (reports whether it existed)
  • file_memory_ls — list stored file paths
  • file_memory_grep — find files whose content matches a regexp

State is held in a self-contained in-memory AgentFileStore and persisted through the session state bag (loadStore/saveStore keyed by a stateKey), so files survive across Invoking calls of the same session. A per-session mutex (weak-pointer identity keyed, with runtime cleanup) serializes concurrent tool invocations, matching the todo provider.

Why

This ports the .NET Harness/FileMemory/FileMemoryProvider to Go. Go previously had no local file-memory harness — the only memory provider (foundryprovider/memory.go) is remote Foundry-store injection with no file tools and no local backend. The wiring mirrors the existing agent/harness/todo provider exactly (agent.NewContextProvider + Provide returning WithTool options + session-state persistence), keeping the harness family consistent and aligning the Go SDK with the .NET surface.

How it's tested

Black-box tests in filememory_test.go (package filememory_test), reusing the same harness style as todo_test.go (agenttest.CreateSession, driving tools via tool.FuncTool.Call):

  • Invoking returns the five file_memory_* tools as WithTool options plus non-empty instructions
  • drive the store through write -> read -> ls -> grep and assert content and match filtering
  • overwrite, delete (present and missing), empty-session listing, invalid-regexp error
  • state persists across a second Invoking via the session state bag
  • concurrent writes + reads under -race

go build ./..., go vet ./agent/harness/filememory/..., and go test -race ./agent/harness/filememory/... all pass.

Open design questions

  • Scope: keeps the store fully in-memory and self-contained (no dependency on a shared/not-yet-existing store package). Should AgentFileStore instead live in a shared package so multiple providers can reuse it?
  • API shape: is a pluggable AgentFileStore (constructor option to inject a custom backend) desirable now, or should that wait for a concrete second backend (e.g. disk-backed)? Currently only the default in-memory store is wired.
  • Tool surface: this ships the minimum useful set (write/read/delete/ls/grep). Follow-ups could add move/copy or content-append if the .NET provider exposes them.

@github-actions

This comment has been minimized.

@github-actions github-actions Bot 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.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

Generated by Go API Consistency Review Agent · 69.7 AIC · ⌖ 4.49 AIC · ⊞ 5.7K

var _ AgentFileStore = (*memoryStore)(nil)

// WriteInput is the input for the file_memory_write tool.
type WriteInput struct {

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.

Parity gap — WriteInput missing description field

Both the .NET FileMemoryProvider.WriteAsync and the Python file_memory_write function accept an optional description parameter. This description is written as a companion _description.md file and is surfaced by file_memory_ls entries so the agent can discover what a file contains without reading it.

The Go WriteInput struct has only Path and Content. The upstream shape (both .NET and Python) is equivalent to:

type WriteInput struct {
	Path        string `json:"path"`
	Content     string `json:"content"`
	Description string `json:"description,omitempty"` // optional file summary for discovery
}

Upstream references:

  • .NET: FileMemoryProvider.WriteAsync(string fileName, string content, string? description = null, ...) in dotnet/src/Microsoft.Agents.AI/Harness/FileMemory/FileMemoryProvider.cs
  • Python: file_memory_write(file_name, content, description='', ...) in python/packages/core/agent_framework/_harness/_file_memory.py


lsTool := functool.MustNew(
functool.Config{
Name: "file_memory_ls",

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.

Parity gap — file_memory_ls returns []string instead of entries with descriptions

Upstream file_memory_ls in both .NET and Python returns a list of file entries that include the file name and its description (when one was written). The description field is what makes discovery practical for large sessions — the agent can read the index without opening every file.

The Go tool returns only paths ([]string). To align with upstream, file_memory_ls should return objects that carry at least {path, description}, and the AgentFileStore interface should expose a ListEntries() method that surfaces stored descriptions.

Upstream references:

  • .NET: FileListEntry type and file_memory_ls returning IReadOnlyList<FileListEntry> in dotnet/src/Microsoft.Agents.AI/Harness/FileMemory/FileListEntry.cs
  • Python: file_memory_ls returning list[dict] with name and description keys in python/packages/core/agent_framework/_harness/_file_memory.py

},
)

return []tool.FuncTool{writeTool, readTool, deleteTool, lsTool, grepTool}

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.

Parity gap — file_memory_replace and file_memory_replace_lines tools are missing

Both the .NET and Python implementations expose two additional editing tools that are absent from this Go port:

  • file_memory_replace — replaces occurrences of a substring within a named file in place, avoiding a full overwrite read-modify-write at the call site
  • file_memory_replace_lines — replaces whole-line ranges within a file, enabling targeted edits to large stored documents

These tools are listed in the upstream AIContextProvider docs and instruction text as first-class capabilities. Agents prompted to use file_memory_replace or file_memory_replace_lines (e.g., when sharing prompts or examples across SDKs) will fail silently in Go because those tool names are simply not registered.

The AgentFileStore interface and the createTools helper should each be extended by two entries to close the gap.

Upstream references:

  • .NET: ReplaceToolName = "file_memory_replace" and ReplaceLinesToolName = "file_memory_replace_lines" in dotnet/src/Microsoft.Agents.AI/Harness/FileMemory/FileMemoryProvider.cs
  • Python: file_memory_replace and file_memory_replace_lines async functions in python/packages/core/agent_framework/_harness/_file_memory.py

@github-actions github-actions Bot added the public-api-change Pull Request changes public APIs label Jul 24, 2026
…ry_* tools

Add an agent/harness/filememory package that mirrors the todo harness
provider wiring. It exposes file_memory_write, file_memory_read,
file_memory_delete, file_memory_ls, and file_memory_grep tools backed by a
self-contained in-memory AgentFileStore persisted through the session state
bag, so files survive across invocations of the same session. This ports the
.NET Harness FileMemoryProvider to Go for cross-SDK parity.
@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 harness context provider (agent/harness/filememory) that exposes session-scoped “virtual file” tools backed by in-memory state persisted in agent.Session, aligning Go’s harness surface with the existing .NET FileMemory provider concept.

Changes:

  • Introduces filememory.Provider with five tools: file_memory_write/read/delete/ls/grep, with per-session locking and session-state persistence.
  • Implements an in-memory AgentFileStore (memoryStore) that is serialized into the session state bag across invocations.
  • Adds black-box tests validating tool availability, store behavior (write/read/ls/grep/delete), persistence across invocations, invalid regexp handling, and concurrent access under -race.

Reviewed changes

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

File Description
agent/harness/filememory/filememory.go New FileMemory context provider + in-memory store + per-session locking + tool wiring.
agent/harness/filememory/filememory_test.go New tests covering tool exposure, functional behavior, persistence, and concurrency safety.
Suppressed comments (1)

agent/harness/filememory/filememory_test.go:225

  • Same as above: prefer an exact comparison to "false" rather than substring matching for the delete-missing case to avoid false positives.
	// Deleting a missing file reports false.
	missing := callTool(t, outOpts, "file_memory_delete", `{"Arg0":"gone.txt"}`)
	if !strings.Contains(missing, "false") {
		t.Errorf("expected delete of missing file to report false, got %q", missing)
	}

Comment on lines +256 to +266
readTool := functool.MustNew(
functool.Config{
Name: "file_memory_read",
Description: "Read the content of a file previously written to session memory. Returns an empty string if the file does not exist.",
},
func(ctx context.Context, path string) (string, error) {
mu := p.getSessionLock(opts)
mu.Lock()
defer mu.Unlock()
content, _ := p.loadStore(opts).Read(path)
return content, nil
Comment on lines +213 to +216
removed := callTool(t, outOpts, "file_memory_delete", `{"Arg0":"gone.txt"}`)
if !strings.Contains(removed, "true") {
t.Errorf("expected delete to report true, got %q", removed)
}
@github-actions

This comment has been minimized.

@github-actions github-actions Bot 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.

Generated by Go API Consistency Review Agent · sonnet46 · 41.3 AIC · ⌖ 4.99 AIC · ⊞ 5.7K

)

return []tool.FuncTool{writeTool, readTool, deleteTool, lsTool, grepTool}
}

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.

Parity gap: missing file_memory_replace and file_memory_replace_lines tools

Both the .NET (FileMemoryProvider.cs, ReplaceToolName / ReplaceLinesToolName) and Python (_file_memory.py, file_memory_replace / file_memory_replace_lines) implementations expose two additional tools that this Go port does not include:

  • file_memory_replace — replaces occurrences of a substring within a file without rewriting the whole content.
  • file_memory_replace_lines — replaces individual lines by number/pattern.

These tools are important for the typical agent workflow (incremental edits to stored plans/notes). Omitting them means agents using the Go SDK cannot perform in-place edits that .NET and Python agents can, leading to divergent agent behaviour.

Upstream references:

  • .NET: dotnet/src/Microsoft.Agents.AI/Harness/FileMemory/FileMemoryProvider.cspublic const string ReplaceToolName and public const string ReplaceLinesToolName
  • Python: python/packages/core/agent_framework/_harness/_file_memory.pyfile_memory_replace and file_memory_replace_lines tools

Suggested action: Add both tools before merging, or open a follow-up issue tracking the gap so it is not forgotten.

var _ AgentFileStore = (*memoryStore)(nil)

// WriteInput is the input for the file_memory_write tool.
type WriteInput struct {

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.

Parity gap: file_memory_write missing optional description parameter

Both upstream implementations accept an optional description string on write, which is stored in a companion <filename>_description.md file and surfaced by file_memory_ls:

  • .NET: WriteAsync(string fileName, string content, string? description = null, ...)
  • Python: file_memory_write(file_name: str, content: str, description: str | None = None) -> str

Go's WriteInput only has Path and Content, so the description feature is absent. This makes file_memory_ls less useful for agents managing many files (no human-readable summaries) and prevents Go agents from producing the same discovery-oriented metadata that .NET and Python agents produce.

Upstream references:

  • .NET: dotnet/src/Microsoft.Agents.AI/Harness/FileMemory/FileMemoryProvider.csWriteAsync signature
  • Python: python/packages/core/agent_framework/_harness/_file_memory.py_WriteFileInput schema

Suggested action: Add an optional Description string field to WriteInput and persist a companion <path>_description.md file, or open a follow-up issue.

scratch data, and intermediate results while working on a task. Files written
here persist across turns of the current session.

Use these tools to manage your files:

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.

Parity gap: default instructions missing proactive index-check guidance

Upstream providers maintain a memories.md index that is injected into the agent context on each turn so the agent knows what files exist without calling file_memory_ls explicitly. The default instructions in both upstream implementations also explicitly tell the agent to check existing memories before starting new tasks:

"Before starting new tasks, use file_memory_ls and file_memory_grep to check for relevant existing memories to avoid duplicate work."

The Go default instructions omit both the proactive-check guidance and the index injection, so Go agents are more likely to duplicate work or fail to build on prior session state.

Upstream references:

  • .NET: dotnet/src/Microsoft.Agents.AI/Harness/FileMemory/FileMemoryProvider.csDefaultInstructions constant and ProvideAIContextAsync (index injection)
  • Python: python/packages/core/agent_framework/_harness/_file_memory.pyDEFAULT_FILE_MEMORY_INSTRUCTIONS and before_run (index injection)

Suggested action: (a) Update defaultInstructions to include the proactive-check guidance, and (b) inject a memories.md-style summary listing current files into the agent context messages in provide().

@gdams

Copy link
Copy Markdown
Member

PratikDhanave (@PratikDhanave) can you resolve the parity gaps?

@github-actions github-actions Bot added area:agent Changes files in the agent area size:xlarge More than 300 changed lines or 10 files pending-auto-risk Automatic risk classification is in progress labels Aug 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Cross-repo Parity Review — filememory harness provider

Upstream reference: .NET dotnet/src/Microsoft.Agents.AI/Harness/FileMemory/FileMemoryProvider.cs · Python python/packages/core/agent_framework/_harness/_file_memory.py

The Go port is a good structural start — the provider wiring, session-state persistence, and in-memory AgentFileStore are all well-aligned with the upstream pattern. However, three parity issues were found that affect the observable agent contract, not just implementation details:

❌ Issues found

# Issue Upstream source Inline comment
1 file_memory_replace and file_memory_replace_lines tools are missing Both .NET and Python Line 8
2 file_memory_write lacks the optional description parameter and sidecar _description.md logic Both .NET and Python Line 120
3 memories.md index is never maintained or injected into agent context Both .NET and Python Line 229

Issue 1 is the highest-impact gap: the two missing tools are named explicitly in the upstream default instructions, so agents following the standard system prompt will attempt to call file_memory_replace and receive a tool-not-found error.

Issue 2 is closely related: the description field is what populates file_memory_ls summaries and the memories.md index (Issue 3). Addressing 2 and 3 together restores the full discovery loop that upstream agents rely on.

What is aligned ✅

  • file_memory_write, file_memory_read, file_memory_delete, file_memory_ls, file_memory_grep tool names and semantics match upstream exactly.
  • Session-state persistence (loadStore/saveStore via the session state bag) is semantically equivalent to the .NET ProvideAIContextAsync state loading.
  • Per-session mutex via weak-pointer identity is an idiomatic Go equivalent of the concurrent serialisation upstream uses.
  • The exported AgentFileStore interface and pluggable-backend design match the upstream abstraction (though Go's store is currently always in-memory, which is an explicitly acknowledged open design question in the PR description).
  • public-api-change label is correctly present since this PR adds a new exported package and public types.

parity-approved cannot be added until the three issues above are resolved or explicitly accepted as intentional divergence with a documented rationale.

Generated by Go API Consistency Review Agent · sonnet46 · 56.8 AIC · ⌖ 4.82 AIC · ⊞ 6K ·

@github-actions github-actions Bot 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.

Generated by Go API Consistency Review Agent · sonnet46 · 56.8 AIC · ⌖ 4.82 AIC · ⊞ 6K

// intermediate results across turns of a long-running task.
//
// The provider exposes five tools to the agent: file_memory_write,
// file_memory_read, file_memory_delete, file_memory_ls, and

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.

Parity gap: missing file_memory_replace and file_memory_replace_lines tools

Both the .NET FileMemoryProvider and the Python FileMemoryProvider expose seven tools; this PR ships only five:

Tool .NET Python Go (this PR)
file_memory_write
file_memory_read
file_memory_delete
file_memory_ls
file_memory_grep
file_memory_replace ❌ missing
file_memory_replace_lines ❌ missing

Upstream references:

  • .NET: FileMemoryProvider.cs — constants ReplaceToolName = "file_memory_replace" and ReplaceLinesToolName = "file_memory_replace_lines"
  • Python: _file_memory.pyfile_memory_replace and file_memory_replace_lines tool definitions

These two tools are not cosmetic additions. They are referenced by name in the default instructions of both upstream SDKs ("use file_memory_replace and file_memory_replace_lines to make small edits"), so agents that follow the standard system prompt will attempt to call them. Omitting them means the Go agent diverges at the behavioral contract level, not just the API surface level.

var _ AgentFileStore = (*memoryStore)(nil)

// WriteInput is the input for the file_memory_write tool.
type WriteInput struct {

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.

Parity gap: file_memory_write missing optional description parameter

Both upstream implementations accept an optional description argument on file_memory_write and use it to:

  1. Store a companion *_description.md sidecar file alongside the main content file.
  2. Surface that description in file_memory_ls output and the auto-maintained memories.md index, allowing the agent to discover file contents without reading them in full.

The Go WriteInput struct only carries Path and Content; it has no description field:

type WriteInput struct {
    Path    string `json:"path"`
    Content string `json:"content"`
}

Upstream .NET signature (from FileMemoryProvider.cs):

[Description("Write a memory file ... Include a description for large files ...")]
private async Task<string> WriteAsync(string fileName, string content, string? description = null, ...)

Upstream Python WriteInput (from _file_memory.py):

description: Annotated[str | None, Field(description="Optional summary used to aid future discovery ...")]

The description field enables the memories.md index mechanism (see next comment). Omitting it means files written by a Go agent carry no description metadata, and file_memory_ls cannot surface per-file summaries.

func (p *Provider) provide(ctx context.Context, invoking agent.InvokingContext) ([]*message.Message, []agent.Option, error) {
opts := invoking.Options
tools := p.createTools(opts)

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.

Parity gap: memories.md index is not maintained

Both .NET and Python automatically maintain a memories.md index file that is injected into the agent context on every Invoking call. The index lists each stored file with its description, letting the agent know what memories are available without exhausting its context window reading every file.

.NET (FileMemoryProvider.cs):

string indexPath = CombinePaths(state.WorkingFolder, MemoryIndexFileName);
string? indexContent = await this._fileStore.ReadAsync(indexPath, ...);
if (!string.IsNullOrWhiteSpace(indexContent))
    // injected into AIContext as a system message

Python (_file_memory.py):

_MEMORY_INDEX_FILE_NAME = "memories.md"
# rebuilt on every write/delete and injected as a system prompt addition

The Go provide function injects only the static instruction string and the tools; it never reads back or injects a memories.md context message. This means a Go agent has no passive visibility into what files exist at the start of a turn — it must always call file_memory_ls first, whereas .NET/Python agents receive that summary for free in their context.

const stateKey = "fileMemoryProviderState"

const defaultInstructions = `## File Memory

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.

Parity gap: default instructions omit file_memory_replace / file_memory_replace_lines guidance

Because the two replace tools are missing, the Go default instructions also omit the guidance both upstream SDKs include:

.NET / Python default instructions both say:

Keep memories up-to-date by overwriting files when information changes, or by using file_memory_replace and file_memory_replace_lines to make small edits.

The Go instructions only list five tools and say nothing about in-place editing. If the replace tools are added (see comment on line 8), the instructions should be updated accordingly.

@github-actions github-actions Bot added failed-auto-risk Automatic risk classification was inconclusive or failed and removed pending-auto-risk Automatic risk classification is in progress labels Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:agent Changes files in the agent area failed-auto-risk Automatic risk classification was inconclusive or failed public-api-change Pull Request changes public APIs size:xlarge More than 300 changed lines or 10 files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants