Sagents.FileSystem (Sagents v0.12.0)
Copy MarkdownPublic API for filesystem lifecycle management.
Provides convenience functions for starting, stopping, and accessing filesystem instances independent of agent lifecycles.
Filesystem Scopes
Filesystems are identified by scope keys (tuples) that determine their lifecycle:
{:user, user_id}- User-scoped filesystem{:project, project_id}- Project-scoped filesystem{:organization, org_id}- Organization-scoped filesystem{:agent, agent_id}- Agent-scoped filesystem (backward compatible)
Usage
# Start a filesystem with a specific directory (idempotent)
{:ok, config} = FileSystemConfig.new(%{
base_directory: "Documents",
persistence_module: Disk,
storage_opts: [path: "/data/users/123"]
})
{:ok, pid} = FileSystem.ensure_filesystem({:user, 123}, [config])
# Or use a default config that catches all paths (no base_directory needed)
{:ok, config} = FileSystemConfig.new(%{
default: true,
persistence_module: MyApp.DBPersistence,
storage_opts: [user_id: 123]
})
{:ok, pid} = FileSystem.ensure_filesystem({:user, 123}, [config])
# Check if running
true = FileSystem.filesystem_running?({:user, 123})
# Get PID
{:ok, pid} = FileSystem.get_filesystem_pid({:user, 123})
# Get scope from PID
{:user, 123} = FileSystem.get_scope(pid)
# Stop filesystem
:ok = FileSystem.stop_filesystem({:user, 123})
Summary
Functions
Ensure a filesystem is running for the given scope (idempotent).
Whether a filesystem is running for scope_key, without raising.
Check if a filesystem is running for the given scope. Raises on a node whose registry is unavailable.
Get the PID of a running filesystem by scope key.
Get the scope key for a filesystem PID.
List all running filesystems.
Start a new filesystem for the given scope.
Stop a running filesystem.
Functions
Ensure a filesystem is running for the given scope (idempotent).
If the filesystem is already running, returns the existing PID. If not running, starts a new filesystem with the given configs.
Parameters
scope_key- Tuple identifying the filesystem scope (e.g.,{:user, 123})configs- List of FileSystemConfig structsopts- Additional options::supervisor- Supervisor reference (PID or registered name). Defaults toFileSystemSupervisor.
Subscribers receive events via direct send/2 (see
Sagents.FileSystemServer.subscribe/1)
Returns
{:ok, pid}- Filesystem PID (existing or newly started){:error, :registry_unavailable}- this node's registry could not answer, so it cannot tell whether one is already running and deliberately does not start one{:error, reason}- Error starting filesystem
Examples
{:ok, config} = FileSystemConfig.new(%{
scope_key: {:user, 123},
base_directory: "Documents",
persistence_module: Disk,
storage_opts: [path: "/data/users/123"]
})
# First call starts the filesystem
{:ok, pid} = ensure_filesystem({:user, 123}, [config])
# Subsequent calls return the same PID
{:ok, ^pid} = ensure_filesystem({:user, 123}, [config])
Whether a filesystem is running for scope_key, without raising.
The non-raising sibling of filesystem_running?/1, in the same relationship
as Sagents.Session.fetch_running/2 to running?/2:
{:ok, true}/{:ok, false}- the registry answered{:error, :registry_unavailable}- this node's registry could not answer
Prefer this anywhere a web request can reach. {:ok, false} means "start
one"; the error means this node cannot know, so it must not guess.
Examples
{:ok, true} = fetch_filesystem_running({:user, 123})
{:ok, false} = fetch_filesystem_running({:user, 999})
Check if a filesystem is running for the given scope. Raises on a node whose registry is unavailable.
Sagents.RegistryUnavailableError comes out of this whenever this node's
registry cannot answer, which covers the drain window of every rolling
deploy. A boolean has no room for "cannot tell", and false reads as
"nothing is running", which a caller responds to by starting a second
filesystem for a scope that already has one elsewhere. Use
fetch_filesystem_running/1 anywhere a web request can reach.
Parameters
scope_key- Tuple identifying the filesystem scope
Returns
Boolean indicating whether the filesystem is running.
Examples
true = filesystem_running?({:user, 123})
false = filesystem_running?({:user, 999})
Get the PID of a running filesystem by scope key.
Parameters
scope_key- Tuple identifying the filesystem scope
Returns
{:ok, pid}- Filesystem found{:error, :not_found}- Filesystem not running{:error, :registry_unavailable}- this node's registry could not answer
Examples
{:ok, pid} = get_filesystem_pid({:user, 123})
Get the scope key for a filesystem PID.
Parameters
pid- Filesystem process PID
Returns
The scope key tuple, or {:error, reason} if the scope cannot be determined.
Examples
{:ok, {:user, 123}} = get_scope(pid)
List all running filesystems.
Returns
List of {scope_key, pid} tuples.
Raises Sagents.RegistryUnavailableError when this node's registry cannot
answer, rather than reporting an empty list as though it were a real result.
Examples
filesystems = list_filesystems()
# => [{:user, 123, #PID<0.123.0>}, {:project, 456, #PID<0.124.0>}]
Start a new filesystem for the given scope.
Returns an error if a filesystem is already running for this scope.
For idempotent behavior, use ensure_filesystem/3 instead.
Parameters
scope_key- Tuple identifying the filesystem scopeconfigs- List of FileSystemConfig structsopts- Additional options::supervisor- Supervisor reference (PID or registered name). Defaults toFileSystemSupervisor.
Returns
{:ok, pid}- Successfully started filesystem{:error, {:already_started, pid}}- Filesystem already running{:error, reason}- Other error
Examples
{:ok, pid} = start_filesystem({:user, 123}, [config])
Stop a running filesystem.
The filesystem will be gracefully terminated, allowing it to flush any pending writes.
Parameters
scope_key- Tuple identifying the filesystem scopeopts- Additional options::supervisor- Supervisor reference (PID or registered name). Defaults toFileSystemSupervisor.
Returns
:ok- Successfully stopped{:error, :not_found}- Filesystem not running{:error, :registry_unavailable}- this node's registry could not answer
The last one matters more than it looks. This is often a best-effort cleanup called before real work, so it reports rather than raises: a raise here takes the caller's whole operation down with it, turning "the scratch filesystem survived" into "the record was never deleted".
Examples
:ok = stop_filesystem({:user, 123})