I wired together a filesystem, a container integration, an asset transfer path, agent tools, and a preview server to make PromptMotion work. Each part is reasonable on its own. The complexity comes from making all of them agree on where the project files live.
And today, Cloudflare published @cloudflare/computer. It gives a Durable Object a persistent filesystem and lets multiple execution backends operate on the same files. That is almost exactly the abstraction I wish I had when I started PromptMotion.
Cloudflare Computer would not replace PromptMotion's domain logic. It could replace the glue that moves project files between my agent, Durable Object, and render container.
I have not migrated yet. This is my current assessment of which problems Computer could solve, which parts would stay, and how I plan to validate the idea.
I checked the details below against the package README and runnable examples. The repository's docs/ directory is explicitly forward-looking and does not always describe what the package ships today.
Why PromptMotion Needs So Much Infrastructure
PromptMotion turns a text prompt into a video composition. The agent writes a HyperFrames project made from HTML and GSAP timelines. The user sees a live browser preview and can render the same composition to an MP4.
That product flow creates five infrastructure requirements:
- Project files must survive Durable Object restarts.
- The model needs safe tools for reading and editing those files.
- Final rendering needs a Linux container with Chromium and FFmpeg.
- The project and its assets must move into the container, and the MP4 must come back out.
- A browser-facing server must turn project files into an interactive preview.
Today, those requirements use separate paths:
+-> custom file tools
|
Agent -> @cloudflare/shell ---+-> generation archives
|
+-> preview handler -> ProjectAgent RPC -> R2
Workflow -> Sandbox container -> base64 assets -> hf-render
-> base64 MP4 -> R2
The system works, but every boundary introduces another convention or conversion.
Durable files need custom wrappers
Each ProjectAgent Durable Object owns an @cloudflare/shell Workspace backed by its SQLite storage. On top of it, I built:
-
readAllProjectFiles()to turn the workspace into aRecord<string, string>. -
archiveGeneration()andgetGenerationFiles()to maintain historical snapshots. -
list_files,read_file, andwrite_filetools with path guards and extension allowlists.
The filesystem is durable. The application code around it is specific to PromptMotion.
Rendering duplicates the filesystem
The render workflow starts a Cloudflare Container through @cloudflare/sandbox, recreates the project directory, and writes every source file into it. It also reads assets from R2 and writes them into the container.
The RPC boundary does not currently give this path a shared filesystem, so binary files travel as base64 strings. The rendered MP4 makes the same trip in reverse before the workflow uploads it to R2.
Workspace files -> flatten paths -> container files
R2 assets -> base64 -> container files
rendered MP4 -> base64 -> Worker memory -> R2
The workflow also owns the Sandbox ID, sleep timer, and cleanup. A missed destroy() can leave a container running longer than intended.
Previewing is a third file path
The live preview cannot use the render workflow. It needs to respond to browser requests for HTML, JavaScript, and assets.
My main Worker's fetch handler calls the ProjectAgent over RPC, loads an archived generation, injects the HyperFrames runtime fixes, and serves the result. Asset requests use another route that reads from R2.
The agent, renderer, and preview server all consume the same project, but each reaches it differently.
The Abstraction Cloudflare Computer Adds
@cloudflare/computer is a preview package that puts a SQLite-backed virtual filesystem inside a Durable Object. A Workspace has two important surfaces:
-
workspace.fsprovides anode:fs/promises-shaped API for durable files. -
workspace.runtime.exec()runs commands or JavaScript through a configured backend against those files.
Three execution backends currently ship:
- A container backend with a real Linux userland and a FUSE-mounted workspace.
- A Worker shell backend powered by
just-bash. - A Worker JavaScript backend that evaluates an ECMAScript module in a fresh Dynamic Worker.
The important part for PromptMotion is not the number of backends. It is that the host application and each backend operate on one durable file model.
+-> built-in agent tools
|
Agent -> Computer -------+-> Workspace.fs -> preview handler
Workspace |
+-> computerd container -> hf-render
|
+-> MP4 stream -> R2
The preview handler remains, but the agent, preview path, and renderer no longer need separate ways to reconstruct the project.
How Computer Maps to My Problems
One durable filesystem instead of file-copying helpers
Computer's filesystem accepts strings, Uint8Array, and ReadableStream values. Text reads can return UTF-8 strings, while binary reads return streams by default.
await ws.fs.writeFile("/workspace/src/composition.html", content);
const composition = await ws.fs.readFile(
"/workspace/src/composition.html",
"utf8",
);
const projectEntries = await ws.fs.find("/workspace/src");
That replaces the custom Workspace wrapper and gives the preview and render paths the same file API.
Computer also has an opt-in git client backed by isomorphic-git. I could initialize a repository per project and commit each accepted generation instead of copying every file into a snapshot directory.
await ws.git.add({ dir: "/workspace", paths: ["src/"] });
await ws.git.commit({
dir: "/workspace",
message: `generation ${generationId}`,
});
Git is not enabled automatically. The Workspace must be configured with createGitClient(), initialized once, and given an identity before this code works.
Built-in tools replace generic agent glue
@cloudflare/computer/tools provides AI SDK tools named read, write, edit, and ls. An exec tool is available when shell backends are configured.
import { createAITools } from "@cloudflare/computer/tools";
const tools = createAITools({
workspace: ws,
read: { maxBytes: 32 * 1024, maxLines: 800 },
});
I would still keep load_skill, list_assets, and start_preview because they describe PromptMotion behavior. I would also preserve my file-extension policy with a thin wrapper because the built-in write tool supports a byte limit, not an extension allowlist.
This is the boundary I want: Computer owns generic file operations, while PromptMotion owns product policy.
The render container sees the same workspace
The CloudflareContainerBackend runs computerd inside a Cloudflare Container. Before a command runs, it synchronizes the Durable Object state into the container and exposes it at /workspace through FUSE. Changes synchronize back after execution.
The render step could operate on the project without recreating it file by file:
using run = await ws.runtime.exec(
"hf-render . --output out/video.mp4 --quiet",
{
cwd: "/workspace/src",
backend: "render",
encoding: "utf8",
},
);
const { exitCode, stderr } = await run.result();
if (exitCode !== 0) throw new Error(`Render failed: ${stderr}`);
This removes the restore-code-to-container step and the manual Sandbox ID. The backend handles container startup, reconnects, workspace synchronization, and command execution.
It does not remove the container image. I still need Chromium, FFmpeg, hyperframes, chrome-headless-shell, and the hf-render wrapper. The image must also run the computerd daemon. The official container example shows the required Worker, Durable Object, WorkspaceProxy, and container wiring.
Streams can replace base64 transfers
Once the command finishes and its changes synchronize back, the workflow can read the MP4 as a stream and pass it to R2:
const video = await ws.fs.readFile("/workspace/src/out/video.mp4");
await env.VIDEOS_BUCKET.put(
`videos/${userId}/${generationId}.mp4`,
video,
{ httpMetadata: { contentType: "video/mp4" } },
);
This removes the current readFile(..., { encoding: "base64" }), atob, and Uint8Array conversion loop.
There is a trade-off. The Workspace shares the Durable Object's roughly 10 GB storage limit, and the container-side filesystem is held in memory. Published benchmarks also show that large sequential reads through the FUSE mount are much slower than native disk. A direct container-to-R2 upload may still be the better design for large rendered videos.
Computer gives me a stream instead of a base64 blob. I still need to measure whether synchronizing the MP4 through the Workspace is the right path.
R2 mounts unify file access, not HTTP serving
Computer can mount an R2 bucket as a read-only subtree:
mounts: {
"/workspace/assets": R2Bucket(env.ASSETS_BUCKET),
}
This lets workspace.fs read assets through the same interface as project files. Before a container command reads a lazily mounted object, the Durable Object must hydrate it or prefetch the mount so bytes are available to synchronize.
An R2 mount does not create a public /assets/... URL. The preview server still needs an HTTP route that reads the mounted file and returns a response. Computer can replace the direct R2 call inside that route, but not the route itself.
What Computer Does Not Solve
The shared Workspace removes infrastructure glue, not PromptMotion logic.
- HyperFrames runtime patches stay. The normalizer and runtime injection fix behavior in the HyperFrames player. Changing the filesystem does not change that. Moreover, I am not using the latest version of HyperFrames, maybe the updates already resolve this?
-
The render image stays.
hf-renderstill needs Chromium, FFmpeg, andPRODUCER_HEADLESS_SHELL_PATH. - The preview server stays. The Worker JavaScript backend evaluates modules and returns output. It does not expose a module as an HTTP request handler.
- Generation metadata stays. D1 still tracks status, ownership, billing, and the final R2 key.
- R2 stays. Large uploads and final videos still need object storage.
- The workflow decision stays. Computer can execute the render command, but I still need to decide where retries, status transitions, and failure recovery live.
This distinction is important. Computer could make the product easier to maintain without pretending that every part of the product becomes a filesystem call.
The Migration I Would Try
I would validate the abstraction in three stages rather than replacing every layer at once.
1. Move source files first
The first step is replacing @cloudflare/shell with a Computer Workspace while keeping the existing preview and render paths.
I would test:
- Existing project import into
/workspace/src. - Read, write, find, and archive behavior across Durable Object restarts.
- The built-in AI tools with my path and extension policies.
- Git snapshots against real generation histories.
This isolates the storage migration from container and preview changes.
2. Run one render through computerd
Next, I would configure the container backend using the official example as the starting point. The computerd image tag should match the installed @cloudflare/computer version.
The useful measurements are concrete:
- Time to synchronize a typical project and its assets.
- Container startup time for the first render and later renders.
- Time and memory used to synchronize the final MP4 back.
- Behavior when rendering succeeds but post-command synchronization fails.
If MP4 synchronization is too expensive, the container can upload the output directly to R2 while Computer still handles source files and command execution.
3. Point the preview server at the Workspace
Finally, I would keep the current browser-facing routes but replace the custom archive RPC and direct R2 reads with workspace.fs calls.
using ws = await getWorkspace(projectAgent);
const composition = await ws.fs.readFile(
`/workspace/generations/${generationId}/src/composition.html`,
"utf8",
);
The HyperFrames injection remains in the handler. Only the file access path changes.
What About Live Render Progress?
An exec handle is also a ReadableStream of events. If a request owns the execution, it can transform those events into Server-Sent Events and stream command output to the browser.
My current render runs inside an AgentWorkflow, so this is not automatic. A workflow step cannot directly pipe its exec stream into a separate active HTTP response. I would need either:
- A request or agent session that owns the render execution.
- An event relay that persists workflow output and forwards it to connected clients.
Computer provides the stream, but PromptMotion still needs the delivery path.
Where I Am Cautious
@cloudflare/computer is explicitly marked preview-only:
APIs are unstable and the design is subject to change. Suitable for experiments, exploration and prototypes. It is NOT suitable for production use at this time.
That leaves four risks to validate:
-
API churn. I would pin the package and matching
computerdimage versions. -
Migration and rollback. Computer is not API-compatible with
@cloudflare/shell; both directions need an explicit data conversion. - Large-file behavior. Source files match the intended agent-scale workload. Video assets and MP4 output may not.
-
Operational ownership.
SyncRetrySchedulercan persist failed sync work, but the application still owns its Durable Object alarm and retry policy.
The preview status is why I would start with a branch and one real project rather than migrate production data immediately.
The Outcome I Want
PromptMotion currently has three ways to reach the same project files: the agent Workspace, the render workflow, and the preview server. Cloudflare Computer could reduce those paths to one durable Workspace shared by the application and its execution backends.
The potential wins are specific:
- Generic file tools and version history move into the Workspace layer.
- The render container operates on the same project instead of receiving a reconstructed copy.
- Binary data can move as streams instead of base64 strings.
The preview server, HyperFrames patches, render image, workflow policy, and R2 storage remain. That is a feature of the design, not a failure. Those pieces contain PromptMotion's product decisions. Computer could remove the infrastructure code around them.
That is the migration I want to test. I will write a follow-up after running a real PromptMotion render through computerd, with timings and the parts that did not work as expected.
If you are testing Cloudflare Computer for a similar agent workload, feel free to hit me up on Twitter. I would love to compare notes.
Further Reading:
Top comments (0)