Cookbook: a Prefect report after the flow worker exits
Upload a closed report from a Prefect task, then check the same bytes from a reader flow in a fresh process.
A Prefect task can build a report and then the worker exits. The next flow run does not have that local file.
mountable.io is a persistent filesystem for AI agents. This recipe uploads the closed report before the task returns an id. A later process reads that same file.
mountable.io maintains the runnable example, separately from Prefect. The report is synthetic. It holds no customer data. This is not a Prefect result store, a cache, or a recovered workflow.
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/prefect-task-report
uv sync --locked
export MOUNTABLE_FILESYSTEM_ID="your-filesystem-id"
export REPORT_STORE=mountableThe example pins Prefect 3.9.0 and the Python SDK. It does not mount the filesystem, and it does not need a Prefect Cloud 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 flow would choose
its real user. It would still choose the filesystem and the path, and it
would not take either from the task arguments.
Write the report
uv run python report.py writeA successful run prints one line:
report_id=a1b2c3d4e5f60789The id is 16 lowercase hex characters. It is not a filesystem id and it is
not a path. The flow runs two tasks. The first task chooses the id. The
second task writes reports/alice/<id>.txt 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 looks like this, with that run's id:
report_id: a1b2c3d4e5f60789
user: alice
kind: synthetic
item,quantity
alpha,1
beta,2If the upload fails, the task fails. The command exits without a
report_id= line.
The tasks pass the id only. They do not receive a filesystem id or a path.
Read it from a fresh process
Start a new process with the same environment. Then read the report:
uv run python report.py read --report-id "$ID"The reader flow passes that id into one task. The task opens a read-only
handle. A matching run prints one line: match, the path, the SHA-256 and
the size of the stored file. The task prints that line only when those
bytes are 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 liveIt prints match, the remote path, the SHA-256 and the size. 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 task returns is the handoff. The bytes stay in the file. This recipe does not store the report as a Prefect result, and it does not cache the task or resume the original flow run.
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 -qThe tests call the real Prefect flow and task functions with a fake store, and they also start two local processes over a temporary directory. They make no paid cloud calls. They check the report hash, a failed upload, and that a second create leaves the first bytes in place.
Sources: write and run a workflow, checked against Prefect 3.9.0 on 2026-10-27, and the Python SDK.