mountable.io

Cookbook: a Dagster report after the worker exits

Materialize a tiny Dagster asset, upload the closed JSON report, and check the same run id from a fresh process.

A Dagster asset can build a report and then the worker exits. The next process does not have that local file, and it does not have the temporary run record.

mountable.io is a persistent filesystem for AI agents. This recipe uploads the closed JSON report before the asset returns an id. A later process reads that same file.

mountable.io maintains the runnable example, separately from Dagster. The report is synthetic. It holds no customer data. This is not an I/O manager, a storage plugin, a remote run store, or Dagster+.

Prerequisites

Use an existing mountable.io account with a saved payment method, an existing filesystem and an API key that can access it. Storage and file operations follow the usage prices. The example creates no account, filesystem or credits.

You also need Python 3.11 or newer and uv. Set MOUNTABLE_API_KEY in your private environment or secret manager. Never put a key in a command argument.

git clone https://github.com/writeitai/mountable.git
cd mountable/examples/dagster-materialization
uv sync --locked
export MOUNTABLE_FILESYSTEM_ID="your-filesystem-id"
export REPORT_STORE=mountable

The example pins Dagster 1.13.27 and the Python SDK. It runs materialize_to_memory on DagsterInstance.ephemeral(). It does not mount the filesystem, and it does not need a Dagster+ account. For one filesystem and a hard monthly spend cap per end user, follow the capped workspace guide. This recipe uses one existing filesystem and a path your backend chooses.

The report belongs to the fixture user alice. Your own asset would choose its real user. It would still choose the filesystem and the path, and it would not take either from the asset arguments.

Write the report

uv run python report.py write

A successful run prints the report id, the Dagster run id, the asset key, the row count, the rows checksum and the file checksum:

report_id=a1b2c3d4e5f60789
run_id=<dagster-run-id>
asset_key=materialization_report
row_count=2
rows_sha256=<sha256 of the two rows>
sha256=<sha256 of the file>
bytes=<size>

The report id is 16 lowercase hex characters. It is not a filesystem id and it is not a path. One run materializes two assets. synthetic_rows builds two rows in memory. materialization_report writes reports/alice/<id>.json only after it has closed the report, then returns that id. The write uses If-None-Match: *, so an existing file is left as it is.

The file records the run id, the asset key, the two rows, the row count and the checksum of those rows.

If the upload fails, the asset fails. The command exits without a report_id= line.

The asset passes the id only. It does not receive a filesystem id or a path.

Read it from a fresh process

Start a new process with the same environment. Pass the id and the run id the writer printed:

uv run python report.py read --report-id "$ID" --run-id "$RUN_ID"

This process does not open the Dagster instance. It opens a read-only handle and checks the stored run id, asset key, row count, rows checksum and file checksum. A matching run prints one line: match, the path, and those same fields. It prints that line only when the stored JSON is the report the writer uploaded.

A second create of the same id does not replace the file:

uv run python report.py overwrite --report-id "$ID"

That command fails, and the first report stays as it is. A read-only handle cannot write it either:

uv run python report.py read-only-write --report-id "$ID"

One command runs that check for you. It writes the report, reads it from a new process, rejects a replacement, rejects a read-only write, and deletes the file:

uv run python report.py live

It prints match and the fields above. It does not print the API key.

A 402 with filesystem_spending_cap_reached stops that filesystem's billable mountable.io work. Other filesystems keep working.

The id the asset returns is the handoff. The bytes stay in the file. This recipe does not store the report through a Dagster I/O manager, and it does not keep the run after the process exits.

Clean up

uv run python report.py cleanup --report-id "$ID"

This deletes that one file. A file that is already gone is left gone.

Offline checks

From the example directory:

uv run pytest -q

The tests call the real asset functions and materialize_to_memory with a fake store, and they also start two local processes over a temporary directory. They make no paid cloud calls. They check the run id, asset key, row count, checksums, a failed upload, and that a second create leaves the first bytes in place.

Sources: Defining assets, checked against Dagster 1.13.27 on 2026-10-27, and the Python SDK.