Skip to content

Python: [Feature]: AgentFileStore.delete_directory for releasing empty explicit directories #9666

Description

Problem

The harness cleans up session scoped directories when a session ends. AgentFileStore.delete(path) is strictly file-only (a path naming a directory returns False, matching FileSystemAgentFileStore.delete on a real filesystem), so there is currently no public, backend independent way to release an explicitly created empty directory. InMemoryAgentFileStore tracks explicit directories separately from stored files, and without a release path those entries live for the lifetime of the store. This surfaced in review of #8687 and its resubmission #9608, where keeping delete() file-only required a companion operation to preserve the existing session cleanup behavior.

Use Case and Evidence

create_directory(path) is public and backend independent, but its counterpart is missing: after all files under a session directory are deleted, the empty explicit directory remains, and the in-memory store keeps its _explicit_dirs entry indefinitely. Real filesystems solve this with rmdir; the in-memory store needs an equivalent so backend substitution stays unobservable.

Desired Outcome

A public AgentFileStore.delete_directory(path) -> bool that releases an explicitly created, empty directory and returns True, and returns False when the path is missing, names a file, or still holds entries (files or nested explicit directories). The base class should ship a default implementation returning False so third-party stores keep working unchanged until they opt in.

Alternatives Considered

Overloading delete() to also remove empty directories: rejected in #8687 review because it makes backend substitution observable (os.remove raises IsADirectoryError while the in-memory store would have succeeded silently) and breaks the documented file-only contract of delete().

Leaking explicit directories until store teardown: the status quo; the memory growth is small but the asymmetry with create_directory invites bugs.

Proposed Direction

Python: delete_directory on the AgentFileStore base class (default False), InMemoryAgentFileStore removing the explicit-dir entry after checking it holds nothing, and FileSystemAgentFileStore calling rmdir and translating missing/file/nonempty into False. Implemented in #9608 with cross backend parity tests.

Cross language consistency needs a call from the .NET side: whether the .NET IAgentFileStore gains a matching method or whether directory cleanup stays Python-only for now. CC westey (@westey-m) for that decision.

Scope

Python: agent_framework._harness public store contract (and the .NET store contract if parity is wanted).

Activity

  1. added
    .NETUsage: [Issues, PRs], Target: .Net
    pythonUsage: [Issues, PRs], Target: Python
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Oct 24, 2026
  2. changed the title [-][Feature]: AgentFileStore.delete_directory for releasing empty explicit directories[/-] [+].NET: [Feature]: AgentFileStore.delete_directory for releasing empty explicit directories[/+] on Oct 24, 2026
  3. changed the title [-].NET: [Feature]: AgentFileStore.delete_directory for releasing empty explicit directories[/-] [+]Python: [Feature]: AgentFileStore.delete_directory for releasing empty explicit directories[/+] on Oct 24, 2026
  4. removed
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Oct 24, 2026
  5. eavanvalkenburg commented on Oct 24, 2026

    @eavanvalkenburg
    Member

    /plan explore different approaches for the delete directory scenario

  6. github-actions commented on Oct 24, 2026

    @github-actions
    Contributor

    Summary

    The issue asks for a public way to remove an empty directory that was made with create_directory. It proposes AgentFileStore.delete_directory(path) -> bool. It would default to False in the base class, and the in-memory and filesystem stores would override it.

    What I found in the current code

    • InMemoryAgentFileStore.create_directory is a no-op today. It has no explicit-directory tracking, so nothing leaks (_file_access.py). Directories are implied by file paths.
    • The leak described in the issue only exists if the in-memory store starts tracking explicit directories. That tracking is not on main.
    • delete() is file-only on both backends.
    • Python calls create_directory for the file-memory working folder and for a session folder (_file_memory.py, _file_access.py ~L2065). The .NET equivalent is FileMemoryProvider.cs.
    • .NET's in-memory CreateDirectoryAsync is also a no-op (source).
    • I found no code that removes directories today.

    Approaches for the delete-directory scenario

    1. Keep create_directory a no-op in memory, add no delete API. Directories stay implicit, as they are today. There is no leak and no new public surface. The cost is a difference between backends: the filesystem leaves empty directories on disk, and memory never has any. That difference is probably acceptable. Nothing in the repo needs "directory exists" to be observable.
    2. Add delete_directory (the issue's proposal). It is a symmetric API, and the default False keeps third-party stores working. The cost is a new public contract that the in-memory store must track state for, with parity tests on both backends. It also forces a decision about the .NET AgentFileStore, which has abstract members, so adding one is source-compatible only if it is virtual.
    3. Track explicit directories in memory, but clean up internally. For example, the harness drops them when the session store is torn down. This avoids a public API, but backends still behave differently and it needs a hidden hook.
    4. Overload delete() to remove empty directories. I would avoid this. The issue notes it breaks the file-only contract and makes backend swaps observable.
    5. A recursive delete_directory. This is more powerful, but riskier, and it is not what session cleanup needs. I would leave it out.

    Recommendation

    Code changes are only warranted if empty-directory cleanup is a real requirement. The problem as written depends on explicit-directory tracking that main does not have. First decide whether that tracking is needed at all.

    • Preferred: approach 1 (no change). If the directory-tracking change is not required, it removes the problem. Optionally, add a short note in the docstring that create_directory is a no-op for in-memory stores.
    • If real directory semantics are wanted (for example, a store where an empty directory must exist and be listable), go with approach 2, using a non-abstract default.

    Proposed plan (only if approach 2 is chosen)

    No changes have been made. This is a proposal.

    1. Python: add async def delete_directory(self, path) -> bool to AgentFileStore (non-abstract, default False), with a docstring that states the contract: an empty explicit directory returns True; a missing path, a file, or a non-empty directory returns False.
    2. Python in-memory store: track explicit directories and release them in delete_directory. A directory counts as non-empty if it holds files or nested explicit directories. Also make list_children and other readers consistent with this tracking. Mind the case-insensitive keys.
    3. Python filesystem store: use rmdir through the existing safe-path resolver, and map missing, file, and non-empty errors to False.
    4. Call it from the session-cleanup path.
    5. .NET: decide with @westey-m. Either add a virtual Task<bool> DeleteDirectoryAsync with a default of false, or document it as Python-only for now. Cross-language drift in the file store contract should be a conscious choice.
    6. Tests: the same contract tests on both stores (success, missing, file, non-empty files, non-empty nested directory). Also check that delete() on a directory still returns False, and that a custom store that does not override the method returns False.

    The issue is resolved when both built-in stores give identical results for those cases and no explicit-directory state remains after session cleanup.

    Question for the maintainers

    Is explicit-directory tracking in the in-memory store a goal in its own right? If not, approach 1 makes this feature unnecessary.

    Generated by Issue Planning for #9666 · copilot · auto · 19 AIC · ⌖ 0.651 AIC · ⊞ 4.6K · ◷

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

Metadata

Metadata

Labels

.NETUsage: [Issues, PRs], Target: .Netharness[Issues, PRs], Target: harness-level itemspythonUsage: [Issues, PRs], Target: Python

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions