Current section

Files

Jump to
sagents lib sagents file_system_callbacks.ex
Raw

lib/sagents/file_system_callbacks.ex

defmodule Sagents.FileSystemCallbacks do
@moduledoc """
Behavior for file system persistence callbacks.
Implement this behavior to provide custom persistence for filesystem operations.
All callbacks are optional — implement only what you need.
## Scope-first contract
Every callback takes the integrator's scope struct as its first positional argument.
## Example Implementation
defmodule MyApp.FilesystemPersistence do
@behaviour Sagents.FileSystemCallbacks
@impl true
def on_write(%MyApp.Accounts.Scope{user: user}, file_path, content, _context) do
case MyApp.Files.create_or_update(user.id, file_path, content) do
{:ok, file} -> {:ok, %{id: file.id}}
{:error, reason} -> {:error, reason}
end
end
@impl true
def on_read(%MyApp.Accounts.Scope{user: user}, file_path, _context) do
case MyApp.Files.get(user.id, file_path) do
nil -> {:error, :not_found}
file -> {:ok, file.content}
end
end
@impl true
def on_delete(%MyApp.Accounts.Scope{user: user}, file_path, _context) do
case MyApp.Files.delete(user.id, file_path) do
{:ok, _} -> {:ok, %{}}
{:error, reason} -> {:error, reason}
end
end
@impl true
def on_list(%MyApp.Accounts.Scope{user: user}, _context) do
files = MyApp.Files.list_for_user(user.id)
{:ok, Enum.map(files, & &1.path)}
end
end
"""
@type file_path :: String.t()
@type content :: String.t()
@type context :: map()
@type result :: {:ok, term()} | {:error, term()}
@doc """
Called when a file is created or overwritten.
## Parameters
- `scope` - Integrator-defined scope struct (or `nil`). Use to filter DB writes.
- `file_path` - Path of the file being written
- `content` - Full content of the file
- `context` - Filesystem-specific fields (agent_id, session_id, etc.)
## Returns
- `{:ok, metadata}` - Success, optionally with metadata
- `{:error, reason}` - Failure reason
"""
@callback on_write(scope :: term() | nil, file_path, content, context) :: result()
@doc """
Called when a file is read.
If this callback is implemented, it will be called BEFORE checking the
in-memory state. This enables lazy-loading from persistent storage.
## Parameters
- `scope` - Integrator-defined scope struct (or `nil`)
- `file_path` - Path of the file being read
- `context` - Filesystem-specific fields
## Returns
- `{:ok, content}` - File content from storage
- `{:error, :not_found}` - File doesn't exist in storage
- `{:error, reason}` - Other error
"""
@callback on_read(scope :: term() | nil, file_path, context) ::
{:ok, content} | {:error, term()}
@doc """
Called when a file is deleted.
## Parameters
- `scope` - Integrator-defined scope struct (or `nil`)
- `file_path` - Path of the file being deleted
- `context` - Filesystem-specific fields
## Returns
- `{:ok, metadata}` - Success
- `{:error, reason}` - Failure reason
"""
@callback on_delete(scope :: term() | nil, file_path, context) :: result()
@doc """
Called to list all files in persistent storage.
Optional — if not implemented, only in-memory files are listed.
## Parameters
- `scope` - Integrator-defined scope struct (or `nil`)
- `context` - Filesystem-specific fields
## Returns
- `{:ok, [file_path]}` - List of file paths
- `{:error, reason}` - Failure reason
"""
@callback on_list(scope :: term() | nil, context) ::
{:ok, [file_path]} | {:error, term()}
@optional_callbacks [on_write: 4, on_read: 3, on_delete: 3, on_list: 2]
end