Durable local Session Resources (ADR 0021).
Pixir treats attachments and ACP resource links as local resources owned by
the Session, not as Presenter blobs and not as ambient model context. When the
original bytes are available, they stay on disk under
.pixir/sessions/<session_id>/resources/; the Log records only a descriptor
with a Session-local resource_id and, for stored payloads, a byte identity
content_sha256.
Provider-specific shapes such as Responses input_image are projections across
the Leakage Boundary. They are assembled from descriptors only when Pixir
intentionally sends a resource to OpenAI.
Resource path boundaries validate the Session id. Static symlink and same-UID race
hardening for payload paths under .pixir/sessions/<session_id>/resources/ is
deliberately deferred; the Resource store is not an adversarial filesystem sandbox.
Summary
Functions
Copy stored resource payloads referenced in replayed Events from parent to child Session.
Return the Provider-ready data URL for a resource descriptor.
Find one resource descriptor in folded History by Session resource id.
Ingest supported attachment maps into the Session Resource store.
Normalize an operator-supplied local attachment (filesystem path or
file:// URI) into the resource_link map ingest_attachments/3 accepts.
Relative paths resolve against workspace; remote URIs are rejected.
Existence is checked at ingestion, not here.
The single source of the local file:// acceptance rule: empty/localhost
host and a real path, nothing remote. Delegate and Workflow attachment
surfaces share it so their mirrors cannot drift.
Render a compact, text-only descriptor for default replay.
Build a text block for one or more descriptors.
Resolve a descriptor to an on-disk path without reading the payload.
Stage referenced payloads in the child workspace, finalize the complete resource
directory, and run operation.
Types
@type descriptor() :: map()
Functions
@spec copy_referenced_resources(String.t(), String.t(), [map()], keyword()) :: :ok | {:error, map()}
Copy stored resource payloads referenced in replayed Events from parent to child Session.
Link-only descriptors without content_sha256 are skipped. Payload copy uses the same
resource_id and checksum paths under the child Session store. The copy is staged and
finalized as one resource-directory unit. Copy/finalization errors remove only the
operation's unique staging directory. After a non-empty transfer is finalized, the
final child directory is operation-owned and is also removed if the following
operation returns an error.
@spec data_url(String.t(), descriptor(), keyword()) :: {:ok, String.t()} | {:error, map()}
Return the Provider-ready data URL for a resource descriptor.
@spec find_descriptor([map()], String.t()) :: {:ok, descriptor()} | {:error, map()}
Find one resource descriptor in folded History by Session resource id.
@spec ingest_attachments(String.t(), [map()] | nil, keyword()) :: {:ok, [descriptor()]} | {:error, map()}
Ingest supported attachment maps into the Session Resource store.
Callers may pass T3-style image attachment maps (type, name, mimeType,
sizeBytes, dataUrl), Pixir-native snake-case variants, or ACP
resource_link blocks (uri, name, mimeType, size). Local file://
links are copied into Pixir's Session Resource store when readable. Remote or
unsupported links are recorded as link-only descriptors and are not
rehydratable until a later explicit import/fetch records bytes.
Raw base64 and local source paths never appear in the returned descriptors.
@spec local_attachment_link(String.t(), Path.t()) :: {:ok, map()} | {:error, :empty_path | :remote_uri | :uri_query_or_fragment | :invalid_path}
Normalize an operator-supplied local attachment (filesystem path or
file:// URI) into the resource_link map ingest_attachments/3 accepts.
Relative paths resolve against workspace; remote URIs are rejected.
Existence is checked at ingestion, not here.
The single source of the local file:// acceptance rule: empty/localhost
host and a real path, nothing remote. Delegate and Workflow attachment
surfaces share it so their mirrors cannot drift.
@spec render_descriptor(descriptor()) :: String.t()
Render a compact, text-only descriptor for default replay.
@spec render_descriptors([descriptor()]) :: String.t()
Build a text block for one or more descriptors.
@spec resource_path(String.t(), descriptor(), String.t()) :: {:ok, String.t()} | {:error, map()}
Resolve a descriptor to an on-disk path without reading the payload.
@spec with_copied_resources(String.t(), String.t(), [map()], keyword(), (-> :ok | {:ok, term()} | {:error, map()})) :: {:ok, term()} | {:error, map()}
Stage referenced payloads in the child workspace, finalize the complete resource
directory, and run operation.
This is the compensation boundary used by Fork and WarmStart for both shared and
isolated workspaces. Copy and finalization errors remove the unique staging
directory. After a non-empty transfer is finalized, {:error, map()} returned by
operation removes both staging and the operation-owned final child resource
directory. Successful descriptors are not rewritten: replay keeps the original
resource_id, content_sha256, and store_ref evidence while the payload bytes live
at the equivalent child-local path.
Existing final child resources are never deleted as preparation: a non-empty
transfer rejects that collision, while an empty transfer leaves them untouched.
Compensation covers returned errors only. It is not VM-crash atomicity and does
not roll back work performed after operation has returned success.