Skip to content

Python: Harness: session-scoped file_access_store option when session_id is unknown at create_harness_agent time #8539

Closed

Description

@likebean

Summary

create_harness_agent's file_access_store today expects the caller to supply an AgentFileStore whose root is already the desired working directory. Unlike FileMemoryProvider, FileAccessProvider does not isolate by session_id at run time.

In hosts that compile/create the harness agent before an AgentSession exists (AG-UI / multi-turn entrypoints, workflow executors restored later from checkpoint, etc.), the caller cannot honestly put session_id into the store root at create_harness_agent time. Platforms end up inventing workarounds (provisional root + mutating _root_path on first run), which is fragile and outside the public API.

Please add a first-class option so file access can be session-scoped inside the framework, with the store root derived from session_id when the session becomes available—not by asking the caller to bake session_id into the path at create time.

Current behavior

Provider Store root at create Isolation
FileMemoryProvider (file_memory_store) Shared root (default {cwd}/agent-file-memory) Framework derives a working folder from session_id / scope at run time
FileAccessProvider (file_access_store) Caller-supplied root is the working area No session nesting; docs say the store “should already be scoped to the desired folder”

Relevant docs in FileAccessProvider:

Unlike MemoryContextProvider / FileMemoryProvider … FileAccessProvider operates on a shared, persistent store … The store is passed in by the caller and should already be scoped to the desired folder or storage location.

create_harness_agent wiring:

  • file_memory_store=None → default shared FS store; provider isolates per session.
  • file_access_store=... → opt-in shared tools; no equivalent session isolation.

Problem

Call sites often look like:

  1. Build / compile harness agent (tools, providers, middleware).
  2. Later: agent.run(..., session=AgentSession(...)) — this is when session_id is known (or assigned by the framework).

Requiring FileSystemAgentFileStore(f".../{session_id}") at step 1 forces one of:

  • Shared root for all sessions (loses isolation),
  • Recreate the whole harness agent once session_id is known (expensive / awkward with middleware & pools),
  • Host-side hacks that mutate private store fields after create.

FileMemoryProvider already solved the “unknown session at create time” case for memory; file access needs the same class of solution if hosts want per-session workspaces (downloads, shell-adjacent artifacts, user uploads, etc.).

Proposed direction (API sketch — open to better shapes)

Add an opt-in session-scoped mode for file access that keeps create-time API free of session_id, for example:

create_harness_agent(
    client,
    # Option A: base directory; framework creates `{base}/{session_key}/` at run time
    file_access_base_dir=Path(".../workspaces"),
    file_access_session_scoped=True,  # default False to preserve today's shared semantics
)

# or Option B: factory invoked when SessionContext has session_id
create_harness_agent(
    client,
    file_access_store_factory=lambda session_id: FileSystemAgentFileStore(base / session_id),
)

Expectations:

  1. Create time: no session_id required.
  2. Run time: on before_run / first use, framework resolves session_id (same rules as file memory: require a session, treat id as opaque key via _storage_key_segment or equivalent).
  3. Root for tools: the effective FileAccessProvider store root is that session directory (paths relative to it), not base/ with a manual {session_id}/... prefix the model must know about.
  4. Default remains shared: existing file_access_store=... behavior unchanged for backward compatibility.
  5. Optionally align with file_memory: allow a shared parent root + framework-owned session subfolder, rather than forcing hosts to pre-bake the session path.

Alternatives considered

  • Keep requiring hosts to pass a session-rooted store → doesn't work when session is created inside run / AG-UI / checkpoint restore.
  • Recreate agent per session → works but is heavy and duplicates wiring.
  • Host mutates FileSystemAgentFileStore._root_path after create → works today as a private workaround; should not be the recommended pattern.

Environment

  • Package: agent-framework (Python harness: create_harness_agent, FileAccessProvider, FileMemoryProvider)
  • Motivation: multi-request hosts that compile agents once per HTTP/SSE request (or once per workflow graph) before the framework AgentSession.session_id is available.

Thanks!

Activity

  1. added
    pythonUsage: [Issues, PRs], Target: Python
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Sep 19, 2026
  2. changed the title [-]Harness: session-scoped file_access_store option when session_id is unknown at create_harness_agent time[/-] [+]Python: Harness: session-scoped file_access_store option when session_id is unknown at create_harness_agent time[/+] on Sep 19, 2026
  3. JHf0912 commented on Sep 19, 2026

    @JHf0912
    Contributor

    Hi — I'd like to take this on. The direction fits the framework's existing pattern, and I'd like to confirm the API shape before opening a PR.

    Proposed shape (primary): mirror FileMemoryProvider exactly.

    • FileAccessProvider(..., session_scoped: bool = False, scope: str | None = None) — default False keeps today's shared-store semantics untouched.
    • When enabled, before_run derives the session folder with the same recipe as _file_memory.py's _resolve_working_folder: self.scope or context.session_id → fail-closed ValueError when neither exists → _storage_key_segment(raw, encoded_prefix="~session-"). The existing guidance in _normalize_relative_path (_file_access.py:246-252) already points at _storage_key_segment for session-derived namespaces.
    • Tool closures prefix every store call with the session key and re-root search/list results back to session-relative names; before_run also creates the session directory. The store's existing symlink/traversal guards continue to apply to the prefixed path, so root_path stays immutable and caller-owned.
    • create_harness_agent(..., file_access_session_scoped: bool = False) wires it through.

    Alternative (if prefixing semantics are unwelcome): a file_access_store_factory: Callable[[str], AgentFileStore] | None that builds the per-session store at before_run (the issue's option B). I'd rather not add a new callable parameter unless the prefix approach is rejected.

    Testing: reuse the injectivity/collision suite already used for file memory isolation (COLLIDING_IDENTIFIERS attack strings: trailing slashes, .., case/Unicode folding), plus fail-closed coverage for missing session_id and a regression check that session_scoped=False preserves current behavior.

    Does this direction work for you, or would the core team prefer the factory shape (or something else, e.g. framework-owned file_access_base_dir from the issue's option A)?

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    pythonUsage: [Issues, PRs], Target: PythontriageUsage: [Issues], Target: All issues that still need to be triaged

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions